1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.app.SearchManager;
20 import android.appwidget.AppWidgetProviderInfo;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.ContentProviderOperation;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.Intent.ShortcutIconResource;
29 import android.content.IntentFilter;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ProviderInfo;
32 import android.content.pm.ResolveInfo;
33 import android.database.Cursor;
34 import android.graphics.Bitmap;
35 import android.net.Uri;
36 import android.os.Build;
37 import android.os.Environment;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.os.Looper;
41 import android.os.Parcelable;
42 import android.os.Process;
43 import android.os.SystemClock;
44 import android.os.TransactionTooLargeException;
45 import android.provider.BaseColumns;
46 import android.text.TextUtils;
47 import android.util.Log;
48 import android.util.LongSparseArray;
49 import android.util.Pair;
50
51 import com.android.launcher3.compat.AppWidgetManagerCompat;
52 import com.android.launcher3.compat.LauncherActivityInfoCompat;
53 import com.android.launcher3.compat.LauncherAppsCompat;
54 import com.android.launcher3.compat.PackageInstallerCompat;
55 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
56 import com.android.launcher3.compat.UserHandleCompat;
57 import com.android.launcher3.compat.UserManagerCompat;
58 import com.android.launcher3.model.WidgetsModel;
59 import com.android.launcher3.util.ComponentKey;
60 import com.android.launcher3.util.CursorIconInfo;
61 import com.android.launcher3.util.LongArrayMap;
62 import com.android.launcher3.util.ManagedProfileHeuristic;
63 import com.android.launcher3.util.Thunk;
64
65 import java.lang.ref.WeakReference;
66 import java.net.URISyntaxException;
67 import java.security.InvalidParameterException;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.Collection;
71 import java.util.Collections;
72 import java.util.Comparator;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.Iterator;
76 import java.util.List;
77 import java.util.Map.Entry;
78 import java.util.Set;
79
80 /**
81 * Maintains in-memory state of the Launcher. It is expected that there should be only one
82 * LauncherModel object held in a static. Also provide APIs for updating the database state
83 * for the Launcher.
84 */
85 public class LauncherModel extends BroadcastReceiver
86 implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
87 static final boolean DEBUG_LOADERS = false;
88 private static final boolean DEBUG_RECEIVER = false;
89 private static final boolean REMOVE_UNRESTORED_ICONS = true;
90
91 static final String TAG = "Launcher.Model";
92
93 public static final int LOADER_FLAG_NONE = 0;
94 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
95 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
96
97 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
98 private static final long INVALID_SCREEN_ID = -1L;
99
100 @Thunk final boolean mAppsCanBeOnRemoveableStorage;
101 private final boolean mOldContentProviderExists;
102
103 @Thunk final LauncherAppState mApp;
104 @Thunk final Object mLock = new Object();
105 @Thunk DeferredHandler mHandler = new DeferredHandler();
106 @Thunk LoaderTask mLoaderTask;
107 @Thunk boolean mIsLoaderTaskRunning;
108 @Thunk boolean mHasLoaderCompletedOnce;
109
110 private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
111
112 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
113 static {
114 sWorkerThread.start();
115 }
116 @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
117
118 // We start off with everything not loaded. After that, we assume that
119 // our monitoring of the package manager provides all updates and we never
120 // need to do a requery. These are only ever touched from the loader thread.
121 @Thunk boolean mWorkspaceLoaded;
122 @Thunk boolean mAllAppsLoaded;
123
124 // When we are loading pages synchronously, we can't just post the binding of items on the side
125 // pages as this delays the rotation process. Instead, we wait for a callback from the first
126 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
127 // a normal load, we also clear this set of Runnables.
128 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
129
130 /**
131 * Set of runnables to be called on the background thread after the workspace binding
132 * is complete.
133 */
134 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
135
136 @Thunk WeakReference<Callbacks> mCallbacks;
137
138 // < only access in worker thread >
139 AllAppsList mBgAllAppsList;
140 // Entire list of widgets.
141 WidgetsModel mBgWidgetsModel;
142
143 // The lock that must be acquired before referencing any static bg data structures. Unlike
144 // other locks, this one can generally be held long-term because we never expect any of these
145 // static data structures to be referenced outside of the worker thread except on the first
146 // load after configuration change.
147 static final Object sBgLock = new Object();
148
149 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
150 // LauncherModel to their ids
151 static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
152
153 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
154 // created by LauncherModel that are directly on the home screen (however, no widgets or
155 // shortcuts within folders).
156 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
157
158 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
159 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
160 new ArrayList<LauncherAppWidgetInfo>();
161
162 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
163 static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
164
165 // sBgWorkspaceScreens is the ordered set of workspace screens.
166 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
167
168 // sBgWidgetProviders is the set of widget providers including custom internal widgets
169 public static HashMap<ComponentKey, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
170
171 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
172 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
173 new HashMap<UserHandleCompat, HashSet<String>>();
174
175 // </ only access in worker thread >
176
177 @Thunk IconCache mIconCache;
178
179 @Thunk final LauncherAppsCompat mLauncherApps;
180 @Thunk final UserManagerCompat mUserManager;
181
182 public interface Callbacks {
183 public boolean setLoadOnResume();
184 public int getCurrentWorkspaceScreen();
185 public void startBinding();
186 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
187 boolean forceAnimateIcons);
188 public void bindScreens(ArrayList<Long> orderedScreenIds);
189 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
190 public void bindFolders(LongArrayMap<FolderInfo> folders);
191 public void finishBindingItems();
192 public void bindAppWidget(LauncherAppWidgetInfo info);
193 public void bindAllApplications(ArrayList<AppInfo> apps);
194 public void bindAppsAdded(ArrayList<Long> newScreens,
195 ArrayList<ItemInfo> addNotAnimated,
196 ArrayList<ItemInfo> addAnimated,
197 ArrayList<AppInfo> addedApps);
198 public void bindAppsUpdated(ArrayList<AppInfo> apps);
199 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
200 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
201 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
202 public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
203 public void bindComponentsRemoved(ArrayList<String> packageNames,
204 ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
205 public void bindAllPackages(WidgetsModel model);
206 public void bindSearchablesChanged();
207 public boolean isAllAppsButtonRank(int rank);
208 public void onPageBoundSynchronously(int page);
209 public void dumpLogsToLocalData();
210 }
211
212 public interface ItemInfoFilter {
213 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
214 }
215
216 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
217 Context context = app.getContext();
218
219 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
220 String oldProvider = context.getString(R.string.old_launcher_provider_uri);
221 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
222 // resource string.
223 String redirectAuthority = Uri.parse(oldProvider).getAuthority();
224 ProviderInfo providerInfo =
225 context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
226 ProviderInfo redirectProvider =
227 context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
228
229 Log.d(TAG, "Old launcher provider: " + oldProvider);
230 mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
231
232 if (mOldContentProviderExists) {
233 Log.d(TAG, "Old launcher provider exists.");
234 } else {
235 Log.d(TAG, "Old launcher provider does not exist.");
236 }
237
238 mApp = app;
239 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
240 mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
241 mIconCache = iconCache;
242
243 mLauncherApps = LauncherAppsCompat.getInstance(context);
244 mUserManager = UserManagerCompat.getInstance(context);
245 }
246
247 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
248 * posted on the main thread handler. */
249 @Thunk void runOnMainThread(Runnable r) {
250 if (sWorkerThread.getThreadId() == Process.myTid()) {
251 // If we are on the worker thread, post onto the main handler
252 mHandler.post(r);
253 } else {
254 r.run();
255 }
256 }
257
258 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
259 * posted on the worker thread handler. */
260 private static void runOnWorkerThread(Runnable r) {
261 if (sWorkerThread.getThreadId() == Process.myTid()) {
262 r.run();
263 } else {
264 // If we are not on the worker thread, then post to the worker handler
265 sWorker.post(r);
266 }
267 }
268
269 /**
270 * Runs the specified runnable after the loader is complete
271 */
272 @Thunk void runAfterBindCompletes(Runnable r) {
273 if (isLoadingWorkspace() || !mHasLoaderCompletedOnce) {
274 synchronized (mBindCompleteRunnables) {
275 mBindCompleteRunnables.add(r);
276 }
277 } else {
278 runOnWorkerThread(r);
279 }
280 }
281
282 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
283 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
284 }
285
286 public void setPackageState(final PackageInstallInfo installInfo) {
287 Runnable updateRunnable = new Runnable() {
288
289 @Override
290 public void run() {
291 synchronized (sBgLock) {
292 final HashSet<ItemInfo> updates = new HashSet<>();
293
294 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
295 // Ignore install success events as they are handled by Package add events.
296 return;
297 }
298
299 for (ItemInfo info : sBgItemsIdMap) {
300 if (info instanceof ShortcutInfo) {
301 ShortcutInfo si = (ShortcutInfo) info;
302 ComponentName cn = si.getTargetComponent();
303 if (si.isPromise() && (cn != null)
304 && installInfo.packageName.equals(cn.getPackageName())) {
305 si.setInstallProgress(installInfo.progress);
306
307 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
308 // Mark this info as broken.
309 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
310 }
311 updates.add(si);
312 }
313 }
314 }
315
316 for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
317 if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
318 widget.installProgress = installInfo.progress;
319 updates.add(widget);
320 }
321 }
322
323 if (!updates.isEmpty()) {
324 // Push changes to the callback.
325 Runnable r = new Runnable() {
326 public void run() {
327 Callbacks callbacks = getCallback();
328 if (callbacks != null) {
329 callbacks.bindRestoreItemsChange(updates);
330 }
331 }
332 };
333 mHandler.post(r);
334 }
335 }
336 }
337 };
338 runOnWorkerThread(updateRunnable);
339 }
340
341 /**
342 * Updates the icons and label of all pending icons for the provided package name.
343 */
344 public void updateSessionDisplayInfo(final String packageName) {
345 Runnable updateRunnable = new Runnable() {
346
347 @Override
348 public void run() {
349 synchronized (sBgLock) {
350 final ArrayList<ShortcutInfo> updates = new ArrayList<>();
351 final UserHandleCompat user = UserHandleCompat.myUserHandle();
352
353 for (ItemInfo info : sBgItemsIdMap) {
354 if (info instanceof ShortcutInfo) {
355 ShortcutInfo si = (ShortcutInfo) info;
356 ComponentName cn = si.getTargetComponent();
357 if (si.isPromise() && (cn != null)
358 && packageName.equals(cn.getPackageName())) {
359 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
360 // For auto install apps update the icon as well as label.
361 mIconCache.getTitleAndIcon(si,
362 si.promisedIntent, user,
363 si.shouldUseLowResIcon());
364 } else {
365 // Only update the icon for restored apps.
366 si.updateIcon(mIconCache);
367 }
368 updates.add(si);
369 }
370 }
371 }
372
373 if (!updates.isEmpty()) {
374 // Push changes to the callback.
375 Runnable r = new Runnable() {
376 public void run() {
377 Callbacks callbacks = getCallback();
378 if (callbacks != null) {
379 callbacks.bindShortcutsChanged(updates,
380 new ArrayList<ShortcutInfo>(), user);
381 }
382 }
383 };
384 mHandler.post(r);
385 }
386 }
387 }
388 };
389 runOnWorkerThread(updateRunnable);
390 }
391
392 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
393 final Callbacks callbacks = getCallback();
394
395 if (allAppsApps == null) {
396 throw new RuntimeException("allAppsApps must not be null");
397 }
398 if (allAppsApps.isEmpty()) {
399 return;
400 }
401
402 // Process the newly added applications and add them to the database first
403 Runnable r = new Runnable() {
404 public void run() {
405 runOnMainThread(new Runnable() {
406 public void run() {
407 Callbacks cb = getCallback();
408 if (callbacks == cb && cb != null) {
409 callbacks.bindAppsAdded(null, null, null, allAppsApps);
410 }
411 }
412 });
413 }
414 };
415 runOnWorkerThread(r);
416 }
417
418 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
419 int[] xy, int spanX, int spanY) {
420 LauncherAppState app = LauncherAppState.getInstance();
421 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
422 final int xCount = (int) profile.numColumns;
423 final int yCount = (int) profile.numRows;
424 boolean[][] occupied = new boolean[xCount][yCount];
425 if (occupiedPos != null) {
426 for (ItemInfo r : occupiedPos) {
427 int right = r.cellX + r.spanX;
428 int bottom = r.cellY + r.spanY;
429 for (int x = r.cellX; 0 <= x && x < right && x < xCount; x++) {
430 for (int y = r.cellY; 0 <= y && y < bottom && y < yCount; y++) {
431 occupied[x][y] = true;
432 }
433 }
434 }
435 }
436 return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
437 }
438
439 /**
440 * Find a position on the screen for the given size or adds a new screen.
441 * @return screenId and the coordinates for the item.
442 */
443 @Thunk Pair<Long, int[]> findSpaceForItem(
444 Context context,
445 ArrayList<Long> workspaceScreens,
446 ArrayList<Long> addedWorkspaceScreensFinal,
447 int spanX, int spanY) {
448 LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
449
450 // Use sBgItemsIdMap as all the items are already loaded.
451 assertWorkspaceLoaded();
452 synchronized (sBgLock) {
453 for (ItemInfo info : sBgItemsIdMap) {
454 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
455 ArrayList<ItemInfo> items = screenItems.get(info.screenId);
456 if (items == null) {
457 items = new ArrayList<>();
458 screenItems.put(info.screenId, items);
459 }
460 items.add(info);
461 }
462 }
463 }
464
465 // Find appropriate space for the item.
466 long screenId = 0;
467 int[] cordinates = new int[2];
468 boolean found = false;
469
470 int screenCount = workspaceScreens.size();
471 // First check the preferred screen.
472 int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
473 if (preferredScreenIndex < screenCount) {
474 screenId = workspaceScreens.get(preferredScreenIndex);
475 found = findNextAvailableIconSpaceInScreen(
476 screenItems.get(screenId), cordinates, spanX, spanY);
477 }
478
479 if (!found) {
480 // Search on any of the screens starting from the first screen.
481 for (int screen = 1; screen < screenCount; screen++) {
482 screenId = workspaceScreens.get(screen);
483 if (findNextAvailableIconSpaceInScreen(
484 screenItems.get(screenId), cordinates, spanX, spanY)) {
485 // We found a space for it
486 found = true;
487 break;
488 }
489 }
490 }
491
492 if (!found) {
493 // Still no position found. Add a new screen to the end.
494 screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
495
496 // Save the screen id for binding in the workspace
497 workspaceScreens.add(screenId);
498 addedWorkspaceScreensFinal.add(screenId);
499
500 // If we still can't find an empty space, then God help us all!!!
501 if (!findNextAvailableIconSpaceInScreen(
502 screenItems.get(screenId), cordinates, spanX, spanY)) {
503 throw new RuntimeException("Can't find space to add the item");
504 }
505 }
506 return Pair.create(screenId, cordinates);
507 }
508
509 /**
510 * Adds the provided items to the workspace.
511 */
512 public void addAndBindAddedWorkspaceItems(final Context context,
513 final ArrayList<? extends ItemInfo> workspaceApps) {
514 final Callbacks callbacks = getCallback();
515 if (workspaceApps.isEmpty()) {
516 return;
517 }
518 // Process the newly added applications and add them to the database first
519 Runnable r = new Runnable() {
520 public void run() {
521 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
522 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
523
524 // Get the list of workspace screens. We need to append to this list and
525 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
526 // called.
527 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
528 synchronized(sBgLock) {
529 for (ItemInfo item : workspaceApps) {
530 if (item instanceof ShortcutInfo) {
531 // Short-circuit this logic if the icon exists somewhere on the workspace
532 if (shortcutExists(context, item.getIntent(), item.user)) {
533 continue;
534 }
535 }
536
537 // Find appropriate space for the item.
538 Pair<Long, int[]> coords = findSpaceForItem(context,
539 workspaceScreens, addedWorkspaceScreensFinal,
540 1, 1);
541 long screenId = coords.first;
542 int[] cordinates = coords.second;
543
544 ItemInfo itemInfo;
545 if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
546 itemInfo = item;
547 } else if (item instanceof AppInfo) {
548 itemInfo = ((AppInfo) item).makeShortcut();
549 } else {
550 throw new RuntimeException("Unexpected info type");
551 }
552
553 // Add the shortcut to the db
554 addItemToDatabase(context, itemInfo,
555 LauncherSettings.Favorites.CONTAINER_DESKTOP,
556 screenId, cordinates[0], cordinates[1]);
557 // Save the ShortcutInfo for binding in the workspace
558 addedShortcutsFinal.add(itemInfo);
559 }
560 }
561
562 // Update the workspace screens
563 updateWorkspaceScreenOrder(context, workspaceScreens);
564
565 if (!addedShortcutsFinal.isEmpty()) {
566 runOnMainThread(new Runnable() {
567 public void run() {
568 Callbacks cb = getCallback();
569 if (callbacks == cb && cb != null) {
570 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
571 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
572 if (!addedShortcutsFinal.isEmpty()) {
573 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
574 long lastScreenId = info.screenId;
575 for (ItemInfo i : addedShortcutsFinal) {
576 if (i.screenId == lastScreenId) {
577 addAnimated.add(i);
578 } else {
579 addNotAnimated.add(i);
580 }
581 }
582 }
583 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
584 addNotAnimated, addAnimated, null);
585 }
586 }
587 });
588 }
589 }
590 };
591 runOnWorkerThread(r);
592 }
593
594 private void unbindItemInfosAndClearQueuedBindRunnables() {
595 if (sWorkerThread.getThreadId() == Process.myTid()) {
596 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
597 "main thread");
598 }
599
600 // Clear any deferred bind runnables
601 synchronized (mDeferredBindRunnables) {
602 mDeferredBindRunnables.clear();
603 }
604
605 // Remove any queued UI runnables
606 mHandler.cancelAll();
607 // Unbind all the workspace items
608 unbindWorkspaceItemsOnMainThread();
609 }
610
611 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
612 void unbindWorkspaceItemsOnMainThread() {
613 // Ensure that we don't use the same workspace items data structure on the main thread
614 // by making a copy of workspace items first.
615 final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
616 synchronized (sBgLock) {
617 tmpItems.addAll(sBgWorkspaceItems);
618 tmpItems.addAll(sBgAppWidgets);
619 }
620 Runnable r = new Runnable() {
621 @Override
622 public void run() {
623 for (ItemInfo item : tmpItems) {
624 item.unbind();
625 }
626 }
627 };
628 runOnMainThread(r);
629 }
630
631 /**
632 * Adds an item to the DB if it was not created previously, or move it to a new
633 * <container, screen, cellX, cellY>
634 */
635 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
636 long screenId, int cellX, int cellY) {
637 if (item.container == ItemInfo.NO_ID) {
638 // From all apps
639 addItemToDatabase(context, item, container, screenId, cellX, cellY);
640 } else {
641 // From somewhere else
642 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
643 }
644 }
645
646 static void checkItemInfoLocked(
647 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
648 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
649 if (modelItem != null && item != modelItem) {
650 // check all the data is consistent
651 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
652 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
653 ShortcutInfo shortcut = (ShortcutInfo) item;
654 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
655 modelShortcut.intent.filterEquals(shortcut.intent) &&
656 modelShortcut.id == shortcut.id &&
657 modelShortcut.itemType == shortcut.itemType &&
658 modelShortcut.container == shortcut.container &&
659 modelShortcut.screenId == shortcut.screenId &&
660 modelShortcut.cellX == shortcut.cellX &&
661 modelShortcut.cellY == shortcut.cellY &&
662 modelShortcut.spanX == shortcut.spanX &&
663 modelShortcut.spanY == shortcut.spanY &&
664 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
665 (modelShortcut.dropPos != null &&
666 shortcut.dropPos != null &&
667 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
668 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
669 // For all intents and purposes, this is the same object
670 return;
671 }
672 }
673
674 // the modelItem needs to match up perfectly with item if our model is
675 // to be consistent with the database-- for now, just require
676 // modelItem == item or the equality check above
677 String msg = "item: " + ((item != null) ? item.toString() : "null") +
678 "modelItem: " +
679 ((modelItem != null) ? modelItem.toString() : "null") +
680 "Error: ItemInfo passed to checkItemInfo doesn't match original";
681 RuntimeException e = new RuntimeException(msg);
682 if (stackTrace != null) {
683 e.setStackTrace(stackTrace);
684 }
685 throw e;
686 }
687 }
688
689 static void checkItemInfo(final ItemInfo item) {
690 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
691 final long itemId = item.id;
692 Runnable r = new Runnable() {
693 public void run() {
694 synchronized (sBgLock) {
695 checkItemInfoLocked(itemId, item, stackTrace);
696 }
697 }
698 };
699 runOnWorkerThread(r);
700 }
701
702 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
703 final ItemInfo item, final String callingFunction) {
704 final long itemId = item.id;
705 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
706 final ContentResolver cr = context.getContentResolver();
707
708 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
709 Runnable r = new Runnable() {
710 public void run() {
711 cr.update(uri, values, null, null);
712 updateItemArrays(item, itemId, stackTrace);
713 }
714 };
715 runOnWorkerThread(r);
716 }
717
718 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
719 final ArrayList<ItemInfo> items, final String callingFunction) {
720 final ContentResolver cr = context.getContentResolver();
721
722 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
723 Runnable r = new Runnable() {
724 public void run() {
725 ArrayList<ContentProviderOperation> ops =
726 new ArrayList<ContentProviderOperation>();
727 int count = items.size();
728 for (int i = 0; i < count; i++) {
729 ItemInfo item = items.get(i);
730 final long itemId = item.id;
731 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
732 ContentValues values = valuesList.get(i);
733
734 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
735 updateItemArrays(item, itemId, stackTrace);
736
737 }
738 try {
739 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
740 } catch (Exception e) {
741 e.printStackTrace();
742 }
743 }
744 };
745 runOnWorkerThread(r);
746 }
747
748 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
749 // Lock on mBgLock *after* the db operation
750 synchronized (sBgLock) {
751 checkItemInfoLocked(itemId, item, stackTrace);
752
753 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
754 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
755 // Item is in a folder, make sure this folder exists
756 if (!sBgFolders.containsKey(item.container)) {
757 // An items container is being set to a that of an item which is not in
758 // the list of Folders.
759 String msg = "item: " + item + " container being set to: " +
760 item.container + ", not in the list of folders";
761 Log.e(TAG, msg);
762 }
763 }
764
765 // Items are added/removed from the corresponding FolderInfo elsewhere, such
766 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
767 // that are on the desktop, as appropriate
768 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
769 if (modelItem != null &&
770 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
771 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
772 switch (modelItem.itemType) {
773 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
774 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
775 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
776 if (!sBgWorkspaceItems.contains(modelItem)) {
777 sBgWorkspaceItems.add(modelItem);
778 }
779 break;
780 default:
781 break;
782 }
783 } else {
784 sBgWorkspaceItems.remove(modelItem);
785 }
786 }
787 }
788
789 /**
790 * Move an item in the DB to a new <container, screen, cellX, cellY>
791 */
792 public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
793 final long screenId, final int cellX, final int cellY) {
794 item.container = container;
795 item.cellX = cellX;
796 item.cellY = cellY;
797
798 // We store hotseat items in canonical form which is this orientation invariant position
799 // in the hotseat
800 if (context instanceof Launcher && screenId < 0 &&
801 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
802 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
803 } else {
804 item.screenId = screenId;
805 }
806
807 final ContentValues values = new ContentValues();
808 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
809 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
810 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
811 values.put(LauncherSettings.Favorites.RANK, item.rank);
812 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
813
814 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
815 }
816
817 /**
818 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
819 * cellX, cellY have already been updated on the ItemInfos.
820 */
821 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
822 final long container, final int screen) {
823
824 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
825 int count = items.size();
826
827 for (int i = 0; i < count; i++) {
828 ItemInfo item = items.get(i);
829 item.container = container;
830
831 // We store hotseat items in canonical form which is this orientation invariant position
832 // in the hotseat
833 if (context instanceof Launcher && screen < 0 &&
834 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
835 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
836 item.cellY);
837 } else {
838 item.screenId = screen;
839 }
840
841 final ContentValues values = new ContentValues();
842 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
843 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
844 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
845 values.put(LauncherSettings.Favorites.RANK, item.rank);
846 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
847
848 contentValues.add(values);
849 }
850 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
851 }
852
853 /**
854 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
855 */
856 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
857 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
858 item.container = container;
859 item.cellX = cellX;
860 item.cellY = cellY;
861 item.spanX = spanX;
862 item.spanY = spanY;
863
864 // We store hotseat items in canonical form which is this orientation invariant position
865 // in the hotseat
866 if (context instanceof Launcher && screenId < 0 &&
867 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
868 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
869 } else {
870 item.screenId = screenId;
871 }
872
873 final ContentValues values = new ContentValues();
874 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
875 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
876 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
877 values.put(LauncherSettings.Favorites.RANK, item.rank);
878 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
879 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
880 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
881
882 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
883 }
884
885 /**
886 * Update an item to the database in a specified container.
887 */
888 public static void updateItemInDatabase(Context context, final ItemInfo item) {
889 final ContentValues values = new ContentValues();
890 item.onAddToDatabase(context, values);
891 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
892 }
893
894 private void assertWorkspaceLoaded() {
895 if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
896 throw new RuntimeException("Trying to add shortcut while loader is running");
897 }
898 }
899
900 /**
901 * Returns true if the shortcuts already exists on the workspace. This must be called after
902 * the workspace has been loaded. We identify a shortcut by its intent.
903 */
904 @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
905 assertWorkspaceLoaded();
906 final String intentWithPkg, intentWithoutPkg;
907 if (intent.getComponent() != null) {
908 // If component is not null, an intent with null package will produce
909 // the same result and should also be a match.
910 String packageName = intent.getComponent().getPackageName();
911 if (intent.getPackage() != null) {
912 intentWithPkg = intent.toUri(0);
913 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
914 } else {
915 intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
916 intentWithoutPkg = intent.toUri(0);
917 }
918 } else {
919 intentWithPkg = intent.toUri(0);
920 intentWithoutPkg = intent.toUri(0);
921 }
922
923 synchronized (sBgLock) {
924 for (ItemInfo item : sBgItemsIdMap) {
925 if (item instanceof ShortcutInfo) {
926 ShortcutInfo info = (ShortcutInfo) item;
927 Intent targetIntent = info.promisedIntent == null
928 ? info.intent : info.promisedIntent;
929 if (targetIntent != null && info.user.equals(user)) {
930 String s = targetIntent.toUri(0);
931 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
932 return true;
933 }
934 }
935 }
936 }
937 }
938 return false;
939 }
940
941 /**
942 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
943 */
944 FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) {
945 final ContentResolver cr = context.getContentResolver();
946 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
947 "_id=? and (itemType=? or itemType=?)",
948 new String[] { String.valueOf(id),
949 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
950
951 try {
952 if (c.moveToFirst()) {
953 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
954 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
955 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
956 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
957 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
958 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
959 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
960
961 FolderInfo folderInfo = null;
962 switch (c.getInt(itemTypeIndex)) {
963 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
964 folderInfo = findOrMakeFolder(folderList, id);
965 break;
966 }
967
968 // Do not trim the folder label, as is was set by the user.
969 folderInfo.title = c.getString(titleIndex);
970 folderInfo.id = id;
971 folderInfo.container = c.getInt(containerIndex);
972 folderInfo.screenId = c.getInt(screenIndex);
973 folderInfo.cellX = c.getInt(cellXIndex);
974 folderInfo.cellY = c.getInt(cellYIndex);
975 folderInfo.options = c.getInt(optionsIndex);
976
977 return folderInfo;
978 }
979 } finally {
980 c.close();
981 }
982
983 return null;
984 }
985
986 /**
987 * Add an item to the database in a specified container. Sets the container, screen, cellX and
988 * cellY fields of the item. Also assigns an ID to the item.
989 */
990 public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
991 final long screenId, final int cellX, final int cellY) {
992 item.container = container;
993 item.cellX = cellX;
994 item.cellY = cellY;
995 // We store hotseat items in canonical form which is this orientation invariant position
996 // in the hotseat
997 if (context instanceof Launcher && screenId < 0 &&
998 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
999 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
1000 } else {
1001 item.screenId = screenId;
1002 }
1003
1004 final ContentValues values = new ContentValues();
1005 final ContentResolver cr = context.getContentResolver();
1006 item.onAddToDatabase(context, values);
1007
1008 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
1009 values.put(LauncherSettings.Favorites._ID, item.id);
1010
1011 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
1012 Runnable r = new Runnable() {
1013 public void run() {
1014 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
1015
1016 // Lock on mBgLock *after* the db operation
1017 synchronized (sBgLock) {
1018 checkItemInfoLocked(item.id, item, stackTrace);
1019 sBgItemsIdMap.put(item.id, item);
1020 switch (item.itemType) {
1021 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1022 sBgFolders.put(item.id, (FolderInfo) item);
1023 // Fall through
1024 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1025 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1026 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
1027 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1028 sBgWorkspaceItems.add(item);
1029 } else {
1030 if (!sBgFolders.containsKey(item.container)) {
1031 // Adding an item to a folder that doesn't exist.
1032 String msg = "adding item: " + item + " to a folder that " +
1033 " doesn't exist";
1034 Log.e(TAG, msg);
1035 }
1036 }
1037 break;
1038 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1039 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
1040 break;
1041 }
1042 }
1043 }
1044 };
1045 runOnWorkerThread(r);
1046 }
1047
1048 /**
1049 * Creates a new unique child id, for a given cell span across all layouts.
1050 */
1051 static int getCellLayoutChildId(
1052 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
1053 return (((int) container & 0xFF) << 24)
1054 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1055 }
1056
1057 private static ArrayList<ItemInfo> getItemsByPackageName(
1058 final String pn, final UserHandleCompat user) {
1059 ItemInfoFilter filter = new ItemInfoFilter() {
1060 @Override
1061 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
1062 return cn.getPackageName().equals(pn) && info.user.equals(user);
1063 }
1064 };
1065 return filterItemInfos(sBgItemsIdMap, filter);
1066 }
1067
1068 /**
1069 * Removes all the items from the database corresponding to the specified package.
1070 */
1071 static void deletePackageFromDatabase(Context context, final String pn,
1072 final UserHandleCompat user) {
1073 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
1074 }
1075
1076 /**
1077 * Removes the specified item from the database
1078 * @param context
1079 * @param item
1080 */
1081 public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1082 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1083 items.add(item);
1084 deleteItemsFromDatabase(context, items);
1085 }
1086
1087 /**
1088 * Removes the specified items from the database
1089 * @param context
1090 * @param item
1091 */
1092 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
1093 final ContentResolver cr = context.getContentResolver();
1094 Runnable r = new Runnable() {
1095 public void run() {
1096 for (ItemInfo item : items) {
1097 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
1098 cr.delete(uri, null, null);
1099
1100 // Lock on mBgLock *after* the db operation
1101 synchronized (sBgLock) {
1102 switch (item.itemType) {
1103 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1104 sBgFolders.remove(item.id);
1105 for (ItemInfo info: sBgItemsIdMap) {
1106 if (info.container == item.id) {
1107 // We are deleting a folder which still contains items that
1108 // think they are contained by that folder.
1109 String msg = "deleting a folder (" + item + ") which still " +
1110 "contains items (" + info + ")";
1111 Log.e(TAG, msg);
1112 }
1113 }
1114 sBgWorkspaceItems.remove(item);
1115 break;
1116 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1117 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1118 sBgWorkspaceItems.remove(item);
1119 break;
1120 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1121 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1122 break;
1123 }
1124 sBgItemsIdMap.remove(item.id);
1125 }
1126 }
1127 }
1128 };
1129 runOnWorkerThread(r);
1130 }
1131
1132 /**
1133 * Update the order of the workspace screens in the database. The array list contains
1134 * a list of screen ids in the order that they should appear.
1135 */
1136 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1137 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1138 final ContentResolver cr = context.getContentResolver();
1139 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1140
1141 // Remove any negative screen ids -- these aren't persisted
1142 Iterator<Long> iter = screensCopy.iterator();
1143 while (iter.hasNext()) {
1144 long id = iter.next();
1145 if (id < 0) {
1146 iter.remove();
1147 }
1148 }
1149
1150 Runnable r = new Runnable() {
1151 @Override
1152 public void run() {
1153 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1154 // Clear the table
1155 ops.add(ContentProviderOperation.newDelete(uri).build());
1156 int count = screensCopy.size();
1157 for (int i = 0; i < count; i++) {
1158 ContentValues v = new ContentValues();
1159 long screenId = screensCopy.get(i);
1160 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1161 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1162 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1163 }
1164
1165 try {
1166 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1167 } catch (Exception ex) {
1168 throw new RuntimeException(ex);
1169 }
1170
1171 synchronized (sBgLock) {
1172 sBgWorkspaceScreens.clear();
1173 sBgWorkspaceScreens.addAll(screensCopy);
1174 }
1175 }
1176 };
1177 runOnWorkerThread(r);
1178 }
1179
1180 /**
1181 * Remove the contents of the specified folder from the database
1182 */
1183 public static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1184 final ContentResolver cr = context.getContentResolver();
1185
1186 Runnable r = new Runnable() {
1187 public void run() {
1188 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
1189 // Lock on mBgLock *after* the db operation
1190 synchronized (sBgLock) {
1191 sBgItemsIdMap.remove(info.id);
1192 sBgFolders.remove(info.id);
1193 sBgWorkspaceItems.remove(info);
1194 }
1195
1196 cr.delete(LauncherSettings.Favorites.CONTENT_URI,
1197 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1198 // Lock on mBgLock *after* the db operation
1199 synchronized (sBgLock) {
1200 for (ItemInfo childInfo : info.contents) {
1201 sBgItemsIdMap.remove(childInfo.id);
1202 }
1203 }
1204 }
1205 };
1206 runOnWorkerThread(r);
1207 }
1208
1209 /**
1210 * Set this as the current Launcher activity object for the loader.
1211 */
1212 public void initialize(Callbacks callbacks) {
1213 synchronized (mLock) {
1214 // Disconnect any of the callbacks and drawables associated with ItemInfos on the
1215 // workspace to prevent leaking Launcher activities on orientation change.
1216 unbindItemInfosAndClearQueuedBindRunnables();
1217 mCallbacks = new WeakReference<Callbacks>(callbacks);
1218 }
1219 }
1220
1221 @Override
1222 public void onPackageChanged(String packageName, UserHandleCompat user) {
1223 int op = PackageUpdatedTask.OP_UPDATE;
1224 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1225 user));
1226 }
1227
1228 @Override
1229 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1230 int op = PackageUpdatedTask.OP_REMOVE;
1231 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1232 user));
1233 }
1234
1235 @Override
1236 public void onPackageAdded(String packageName, UserHandleCompat user) {
1237 int op = PackageUpdatedTask.OP_ADD;
1238 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1239 user));
1240 }
1241
1242 @Override
1243 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1244 boolean replacing) {
1245 if (!replacing) {
1246 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
1247 user));
1248 if (mAppsCanBeOnRemoveableStorage) {
1249 // Only rebind if we support removable storage. It catches the
1250 // case where
1251 // apps on the external sd card need to be reloaded
1252 startLoaderFromBackground();
1253 }
1254 } else {
1255 // If we are replacing then just update the packages in the list
1256 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1257 packageNames, user));
1258 }
1259 }
1260
1261 @Override
1262 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1263 boolean replacing) {
1264 if (!replacing) {
1265 enqueuePackageUpdated(new PackageUpdatedTask(
1266 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1267 user));
1268 }
1269 }
1270
1271 /**
1272 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1273 * ACTION_PACKAGE_CHANGED.
1274 */
1275 @Override
1276 public void onReceive(Context context, Intent intent) {
1277 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
1278
1279 final String action = intent.getAction();
1280 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1281 // If we have changed locale we need to clear out the labels in all apps/workspace.
1282 forceReload();
1283 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
1284 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
1285 Callbacks callbacks = getCallback();
1286 if (callbacks != null) {
1287 callbacks.bindSearchablesChanged();
1288 }
1289 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1290 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1291 forceReload();
1292 }
1293 }
1294
1295 void forceReload() {
1296 resetLoadedState(true, true);
1297
1298 // Do this here because if the launcher activity is running it will be restarted.
1299 // If it's not running startLoaderFromBackground will merely tell it that it needs
1300 // to reload.
1301 startLoaderFromBackground();
1302 }
1303
1304 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1305 synchronized (mLock) {
1306 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1307 // mWorkspaceLoaded to true later
1308 stopLoaderLocked();
1309 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1310 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1311 }
1312 }
1313
1314 /**
1315 * When the launcher is in the background, it's possible for it to miss paired
1316 * configuration changes. So whenever we trigger the loader from the background
1317 * tell the launcher that it needs to re-run the loader when it comes back instead
1318 * of doing it now.
1319 */
1320 public void startLoaderFromBackground() {
1321 boolean runLoader = false;
1322 Callbacks callbacks = getCallback();
1323 if (callbacks != null) {
1324 // Only actually run the loader if they're not paused.
1325 if (!callbacks.setLoadOnResume()) {
1326 runLoader = true;
1327 }
1328 }
1329 if (runLoader) {
1330 startLoader(PagedView.INVALID_RESTORE_PAGE);
1331 }
1332 }
1333
1334 /**
1335 * If there is already a loader task running, tell it to stop.
1336 */
1337 private void stopLoaderLocked() {
1338 LoaderTask oldTask = mLoaderTask;
1339 if (oldTask != null) {
1340 oldTask.stopLocked();
1341 }
1342 }
1343
1344 public boolean isCurrentCallbacks(Callbacks callbacks) {
1345 return (mCallbacks != null && mCallbacks.get() == callbacks);
1346 }
1347
1348 public void startLoader(int synchronousBindPage) {
1349 startLoader(synchronousBindPage, LOADER_FLAG_NONE);
1350 }
1351
1352 public void startLoader(int synchronousBindPage, int loadFlags) {
1353 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
1354 InstallShortcutReceiver.enableInstallQueue();
1355 synchronized (mLock) {
1356 // Clear any deferred bind-runnables from the synchronized load process
1357 // We must do this before any loading/binding is scheduled below.
1358 synchronized (mDeferredBindRunnables) {
1359 mDeferredBindRunnables.clear();
1360 }
1361
1362 // Don't bother to start the thread if we know it's not going to do anything
1363 if (mCallbacks != null && mCallbacks.get() != null) {
1364 // If there is already one running, tell it to stop.
1365 stopLoaderLocked();
1366 mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
1367 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1368 && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
1369 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1370 } else {
1371 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1372 sWorker.post(mLoaderTask);
1373 }
1374 }
1375 }
1376 }
1377
1378 void bindRemainingSynchronousPages() {
1379 // Post the remaining side pages to be loaded
1380 if (!mDeferredBindRunnables.isEmpty()) {
1381 Runnable[] deferredBindRunnables = null;
1382 synchronized (mDeferredBindRunnables) {
1383 deferredBindRunnables = mDeferredBindRunnables.toArray(
1384 new Runnable[mDeferredBindRunnables.size()]);
1385 mDeferredBindRunnables.clear();
1386 }
1387 for (final Runnable r : deferredBindRunnables) {
1388 mHandler.post(r);
1389 }
1390 }
1391
1392 // Run all the bind complete runnables after workspace is bound.
1393 if (!mBindCompleteRunnables.isEmpty()) {
1394 synchronized (mBindCompleteRunnables) {
1395 for (final Runnable r : mBindCompleteRunnables) {
1396 runOnWorkerThread(r);
1397 }
1398 mBindCompleteRunnables.clear();
1399 }
1400 }
1401 }
1402
1403 public void stopLoader() {
1404 synchronized (mLock) {
1405 if (mLoaderTask != null) {
1406 mLoaderTask.stopLocked();
1407 }
1408 }
1409 }
1410
1411 /**
1412 * Loads the workspace screen ids in an ordered list.
1413 */
1414 @Thunk static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1415 final ContentResolver contentResolver = context.getContentResolver();
1416 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1417
1418 // Get screens ordered by rank.
1419 final Cursor sc = contentResolver.query(screensUri, null, null, null,
1420 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1421 ArrayList<Long> screenIds = new ArrayList<Long>();
1422 try {
1423 final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
1424 while (sc.moveToNext()) {
1425 try {
1426 screenIds.add(sc.getLong(idIndex));
1427 } catch (Exception e) {
1428 Launcher.addDumpLog(TAG, "Desktop items loading interrupted"
1429 + " - invalid screens: " + e, true);
1430 }
1431 }
1432 } finally {
1433 sc.close();
1434 }
1435 return screenIds;
1436 }
1437
1438 public boolean isAllAppsLoaded() {
1439 return mAllAppsLoaded;
1440 }
1441
1442 boolean isLoadingWorkspace() {
1443 synchronized (mLock) {
1444 if (mLoaderTask != null) {
1445 return mLoaderTask.isLoadingWorkspace();
1446 }
1447 }
1448 return false;
1449 }
1450
1451 /**
1452 * Runnable for the thread that loads the contents of the launcher:
1453 * - workspace icons
1454 * - widgets
1455 * - all apps icons
1456 */
1457 private class LoaderTask implements Runnable {
1458 private Context mContext;
1459 @Thunk boolean mIsLoadingAndBindingWorkspace;
1460 private boolean mStopped;
1461 @Thunk boolean mLoadAndBindStepFinished;
1462 private int mFlags;
1463
1464 LoaderTask(Context context, int flags) {
1465 mContext = context;
1466 mFlags = flags;
1467 }
1468
1469 boolean isLoadingWorkspace() {
1470 return mIsLoadingAndBindingWorkspace;
1471 }
1472
1473 private void loadAndBindWorkspace() {
1474 mIsLoadingAndBindingWorkspace = true;
1475
1476 // Load the workspace
1477 if (DEBUG_LOADERS) {
1478 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1479 }
1480
1481 if (!mWorkspaceLoaded) {
1482 loadWorkspace();
1483 synchronized (LoaderTask.this) {
1484 if (mStopped) {
1485 return;
1486 }
1487 mWorkspaceLoaded = true;
1488 }
1489 }
1490
1491 // Bind the workspace
1492 bindWorkspace(-1);
1493 }
1494
1495 private void waitForIdle() {
1496 // Wait until the either we're stopped or the other threads are done.
1497 // This way we don't start loading all apps until the workspace has settled
1498 // down.
1499 synchronized (LoaderTask.this) {
1500 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1501
1502 mHandler.postIdle(new Runnable() {
1503 public void run() {
1504 synchronized (LoaderTask.this) {
1505 mLoadAndBindStepFinished = true;
1506 if (DEBUG_LOADERS) {
1507 Log.d(TAG, "done with previous binding step");
1508 }
1509 LoaderTask.this.notify();
1510 }
1511 }
1512 });
1513
1514 while (!mStopped && !mLoadAndBindStepFinished) {
1515 try {
1516 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1517 // wait no longer than 1sec at a time
1518 this.wait(1000);
1519 } catch (InterruptedException ex) {
1520 // Ignore
1521 }
1522 }
1523 if (DEBUG_LOADERS) {
1524 Log.d(TAG, "waited "
1525 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1526 + "ms for previous step to finish binding");
1527 }
1528 }
1529 }
1530
1531 void runBindSynchronousPage(int synchronousBindPage) {
1532 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1533 // Ensure that we have a valid page index to load synchronously
1534 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1535 "valid page index");
1536 }
1537 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1538 // Ensure that we don't try and bind a specified page when the pages have not been
1539 // loaded already (we should load everything asynchronously in that case)
1540 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1541 }
1542 synchronized (mLock) {
1543 if (mIsLoaderTaskRunning) {
1544 // Ensure that we are never running the background loading at this point since
1545 // we also touch the background collections
1546 throw new RuntimeException("Error! Background loading is already running");
1547 }
1548 }
1549
1550 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1551 // data structures, we can't allow any other thread to touch that data, but because
1552 // this call is synchronous, we can get away with not locking).
1553
1554 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1555 // operations from the previous activity. We need to ensure that all queued operations
1556 // are executed before any synchronous binding work is done.
1557 mHandler.flush();
1558
1559 // Divide the set of loaded items into those that we are binding synchronously, and
1560 // everything else that is to be bound normally (asynchronously).
1561 bindWorkspace(synchronousBindPage);
1562 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1563 // arise from that.
1564 onlyBindAllApps();
1565 }
1566
1567 public void run() {
1568 synchronized (mLock) {
1569 if (mStopped) {
1570 return;
1571 }
1572 mIsLoaderTaskRunning = true;
1573 }
1574 // Optimize for end-user experience: if the Launcher is up and // running with the
1575 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1576 // workspace first (default).
1577 keep_running: {
1578 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1579 loadAndBindWorkspace();
1580
1581 if (mStopped) {
1582 break keep_running;
1583 }
1584
1585 waitForIdle();
1586
1587 // second step
1588 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1589 loadAndBindAllApps();
1590 }
1591
1592 // Clear out this reference, otherwise we end up holding it until all of the
1593 // callback runnables are done.
1594 mContext = null;
1595
1596 synchronized (mLock) {
1597 // If we are still the last one to be scheduled, remove ourselves.
1598 if (mLoaderTask == this) {
1599 mLoaderTask = null;
1600 }
1601 mIsLoaderTaskRunning = false;
1602 mHasLoaderCompletedOnce = true;
1603 }
1604 }
1605
1606 public void stopLocked() {
1607 synchronized (LoaderTask.this) {
1608 mStopped = true;
1609 this.notify();
1610 }
1611 }
1612
1613 /**
1614 * Gets the callbacks object. If we've been stopped, or if the launcher object
1615 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1616 * object that was around when the deferred message was scheduled, and if there's
1617 * a new Callbacks object around then also return null. This will save us from
1618 * calling onto it with data that will be ignored.
1619 */
1620 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1621 synchronized (mLock) {
1622 if (mStopped) {
1623 return null;
1624 }
1625
1626 if (mCallbacks == null) {
1627 return null;
1628 }
1629
1630 final Callbacks callbacks = mCallbacks.get();
1631 if (callbacks != oldCallbacks) {
1632 return null;
1633 }
1634 if (callbacks == null) {
1635 Log.w(TAG, "no mCallbacks");
1636 return null;
1637 }
1638
1639 return callbacks;
1640 }
1641 }
1642
1643 // check & update map of what's occupied; used to discard overlapping/invalid items
1644 private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) {
1645 LauncherAppState app = LauncherAppState.getInstance();
1646 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1647 final int countX = (int) profile.numColumns;
1648 final int countY = (int) profile.numRows;
1649
1650 long containerIndex = item.screenId;
1651 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1652 // Return early if we detect that an item is under the hotseat button
1653 if (mCallbacks == null ||
1654 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1655 Log.e(TAG, "Error loading shortcut into hotseat " + item
1656 + " into position (" + item.screenId + ":" + item.cellX + ","
1657 + item.cellY + ") occupied by all apps");
1658 return false;
1659 }
1660
1661 final ItemInfo[][] hotseatItems =
1662 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1663
1664 if (item.screenId >= profile.numHotseatIcons) {
1665 Log.e(TAG, "Error loading shortcut " + item
1666 + " into hotseat position " + item.screenId
1667 + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
1668 + ")");
1669 return false;
1670 }
1671
1672 if (hotseatItems != null) {
1673 if (hotseatItems[(int) item.screenId][0] != null) {
1674 Log.e(TAG, "Error loading shortcut into hotseat " + item
1675 + " into position (" + item.screenId + ":" + item.cellX + ","
1676 + item.cellY + ") occupied by "
1677 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1678 [(int) item.screenId][0]);
1679 return false;
1680 } else {
1681 hotseatItems[(int) item.screenId][0] = item;
1682 return true;
1683 }
1684 } else {
1685 final ItemInfo[][] items = new ItemInfo[(int) profile.numHotseatIcons][1];
1686 items[(int) item.screenId][0] = item;
1687 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1688 return true;
1689 }
1690 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1691 // Skip further checking if it is not the hotseat or workspace container
1692 return true;
1693 }
1694
1695 if (!occupied.containsKey(item.screenId)) {
1696 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1697 occupied.put(item.screenId, items);
1698 }
1699
1700 final ItemInfo[][] screens = occupied.get(item.screenId);
1701 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1702 item.cellX < 0 || item.cellY < 0 ||
1703 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1704 Log.e(TAG, "Error loading shortcut " + item
1705 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1706 + item.cellX + "," + item.cellY
1707 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1708 return false;
1709 }
1710
1711 // Check if any workspace icons overlap with each other
1712 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1713 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1714 if (screens[x][y] != null) {
1715 Log.e(TAG, "Error loading shortcut " + item
1716 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1717 + x + "," + y
1718 + ") occupied by "
1719 + screens[x][y]);
1720 return false;
1721 }
1722 }
1723 }
1724 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1725 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1726 screens[x][y] = item;
1727 }
1728 }
1729
1730 return true;
1731 }
1732
1733 /** Clears all the sBg data structures */
1734 private void clearSBgDataStructures() {
1735 synchronized (sBgLock) {
1736 sBgWorkspaceItems.clear();
1737 sBgAppWidgets.clear();
1738 sBgFolders.clear();
1739 sBgItemsIdMap.clear();
1740 sBgWorkspaceScreens.clear();
1741 }
1742 }
1743
1744 private void loadWorkspace() {
1745 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1746
1747 final Context context = mContext;
1748 final ContentResolver contentResolver = context.getContentResolver();
1749 final PackageManager manager = context.getPackageManager();
1750 final boolean isSafeMode = manager.isSafeMode();
1751 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1752 final boolean isSdCardReady = context.registerReceiver(null,
1753 new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
1754
1755 LauncherAppState app = LauncherAppState.getInstance();
1756 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1757 int countX = (int) profile.numColumns;
1758 int countY = (int) profile.numRows;
1759
1760 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1761 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1762 LauncherAppState.getLauncherProvider().deleteDatabase();
1763 }
1764
1765 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1766 // append the user's Launcher2 shortcuts
1767 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1768 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1769 } else {
1770 // Make sure the default workspace is loaded
1771 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1772 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
1773 }
1774
1775 synchronized (sBgLock) {
1776 clearSBgDataStructures();
1777 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
1778 .getInstance(mContext).updateAndGetActiveSessionCache();
1779
1780 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1781 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1782 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1783 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1784 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1785
1786 // +1 for the hotseat (it can be larger than the workspace)
1787 // Load workspace in reverse order to ensure that latest items are loaded first (and
1788 // before any earlier duplicates)
1789 final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
1790
1791 try {
1792 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1793 final int intentIndex = c.getColumnIndexOrThrow
1794 (LauncherSettings.Favorites.INTENT);
1795 final int titleIndex = c.getColumnIndexOrThrow
1796 (LauncherSettings.Favorites.TITLE);
1797 final int containerIndex = c.getColumnIndexOrThrow(
1798 LauncherSettings.Favorites.CONTAINER);
1799 final int itemTypeIndex = c.getColumnIndexOrThrow(
1800 LauncherSettings.Favorites.ITEM_TYPE);
1801 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1802 LauncherSettings.Favorites.APPWIDGET_ID);
1803 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1804 LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1805 final int screenIndex = c.getColumnIndexOrThrow(
1806 LauncherSettings.Favorites.SCREEN);
1807 final int cellXIndex = c.getColumnIndexOrThrow
1808 (LauncherSettings.Favorites.CELLX);
1809 final int cellYIndex = c.getColumnIndexOrThrow
1810 (LauncherSettings.Favorites.CELLY);
1811 final int spanXIndex = c.getColumnIndexOrThrow
1812 (LauncherSettings.Favorites.SPANX);
1813 final int spanYIndex = c.getColumnIndexOrThrow(
1814 LauncherSettings.Favorites.SPANY);
1815 final int rankIndex = c.getColumnIndexOrThrow(
1816 LauncherSettings.Favorites.RANK);
1817 final int restoredIndex = c.getColumnIndexOrThrow(
1818 LauncherSettings.Favorites.RESTORED);
1819 final int profileIdIndex = c.getColumnIndexOrThrow(
1820 LauncherSettings.Favorites.PROFILE_ID);
1821 final int optionsIndex = c.getColumnIndexOrThrow(
1822 LauncherSettings.Favorites.OPTIONS);
1823 final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
1824
1825 final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
1826 for (UserHandleCompat user : mUserManager.getUserProfiles()) {
1827 allUsers.put(mUserManager.getSerialNumberForUser(user), user);
1828 }
1829
1830 ShortcutInfo info;
1831 String intentDescription;
1832 LauncherAppWidgetInfo appWidgetInfo;
1833 int container;
1834 long id;
1835 long serialNumber;
1836 Intent intent;
1837 UserHandleCompat user;
1838
1839 while (!mStopped && c.moveToNext()) {
1840 try {
1841 int itemType = c.getInt(itemTypeIndex);
1842 boolean restored = 0 != c.getInt(restoredIndex);
1843 boolean allowMissingTarget = false;
1844 container = c.getInt(containerIndex);
1845
1846 switch (itemType) {
1847 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1848 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1849 id = c.getLong(idIndex);
1850 intentDescription = c.getString(intentIndex);
1851 serialNumber = c.getInt(profileIdIndex);
1852 user = allUsers.get(serialNumber);
1853 int promiseType = c.getInt(restoredIndex);
1854 int disabledState = 0;
1855 boolean itemReplaced = false;
1856 if (user == null) {
1857 // User has been deleted remove the item.
1858 itemsToRemove.add(id);
1859 continue;
1860 }
1861 try {
1862 intent = Intent.parseUri(intentDescription, 0);
1863 ComponentName cn = intent.getComponent();
1864 if (cn != null && cn.getPackageName() != null) {
1865 boolean validPkg = launcherApps.isPackageEnabledForProfile(
1866 cn.getPackageName(), user);
1867 boolean validComponent = validPkg &&
1868 launcherApps.isActivityEnabledForProfile(cn, user);
1869
1870 if (validComponent) {
1871 if (restored) {
1872 // no special handling necessary for this item
1873 restoredRows.add(id);
1874 restored = false;
1875 }
1876 } else if (validPkg) {
1877 intent = null;
1878 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
1879 // We allow auto install apps to have their intent
1880 // updated after an install.
1881 intent = manager.getLaunchIntentForPackage(
1882 cn.getPackageName());
1883 if (intent != null) {
1884 ContentValues values = new ContentValues();
1885 values.put(LauncherSettings.Favorites.INTENT,
1886 intent.toUri(0));
1887 updateItem(id, values);
1888 }
1889 }
1890
1891 if (intent == null) {
1892 // The app is installed but the component is no
1893 // longer available.
1894 Launcher.addDumpLog(TAG,
1895 "Invalid component removed: " + cn, true);
1896 itemsToRemove.add(id);
1897 continue;
1898 } else {
1899 // no special handling necessary for this item
1900 restoredRows.add(id);
1901 restored = false;
1902 }
1903 } else if (restored) {
1904 // Package is not yet available but might be
1905 // installed later.
1906 Launcher.addDumpLog(TAG,
1907 "package not yet restored: " + cn, true);
1908
1909 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
1910 // Restore has started once.
1911 } else if (installingPkgs.containsKey(cn.getPackageName())) {
1912 // App restore has started. Update the flag
1913 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1914 ContentValues values = new ContentValues();
1915 values.put(LauncherSettings.Favorites.RESTORED,
1916 promiseType);
1917 updateItem(id, values);
1918 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE🔵
1919 // This is a common app. Try to replace this.
1920 int appType = CommonAppTypeParser.decodeItemTypeFromFlag(🔵
1921 CommonAppTypeParser parser = new CommonAppTypeParser(id, 🔵
1922 if (parser.findDefaultApp()) {
1923 // Default app found. Replace it.
1924 intent = parser.parsedIntent;
1925 cn = intent.getComponent();
1926 ContentValues values = parser.parsedValues;
1927 values.put(LauncherSettings.Favorites.RESTORED, 0);
1928 updateItem(id, values);
1929 restored = false;
1930 itemReplaced = true;
1931
1932 } else if (REMOVE_UNRESTORED_ICONS) {
1933 Launcher.addDumpLog(TAG,
1934 "Unrestored package removed: " + cn, true);
1935 itemsToRemove.add(id);
1936 continue;
1937 }
1938 } else if (REMOVE_UNRESTORED_ICONS) {
1939 Launcher.addDumpLog(TAG,
1940 "Unrestored package removed: " + cn, true);
1941 itemsToRemove.add(id);
1942 continue;
1943 }
1944 } else if (launcherApps.isAppEnabled(
1945 manager, cn.getPackageName(),
1946 PackageManager.GET_UNINSTALLED_PACKAGES)) {
1947 // Package is present but not available.
1948 allowMissingTarget = true;
1949 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1950 } else if (!isSdCardReady) {
1951 // SdCard is not ready yet. Package might get available,
1952 // once it is ready.
1953 Launcher.addDumpLog(TAG, "Invalid package: " + cn
1954 + " (check again later)", true);
1955 HashSet<String> pkgs = sPendingPackages.get(user);
1956 if (pkgs == null) {
1957 pkgs = new HashSet<String>();
1958 sPendingPackages.put(user, pkgs);
1959 }
1960 pkgs.add(cn.getPackageName());
1961 allowMissingTarget = true;
1962 // Add the icon on the workspace anyway.
1963
1964 } else {
1965 // Do not wait for external media load anymore.
1966 // Log the invalid package, and remove it
1967 Launcher.addDumpLog(TAG,
1968 "Invalid package removed: " + cn, true);
1969 itemsToRemove.add(id);
1970 continue;
1971 }
1972 } else if (cn == null) {
1973 // For shortcuts with no component, keep them as they are
1974 restoredRows.add(id);
1975 restored = false;
1976 }
1977 } catch (URISyntaxException e) {
1978 Launcher.addDumpLog(TAG,
1979 "Invalid uri: " + intentDescription, true);
1980 continue;
1981 }
1982
1983 boolean useLowResIcon = container >= 0 &&
1984 c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1985
1986 if (itemReplaced) {
1987 if (user.equals(UserHandleCompat.myUserHandle())) {
1988 info = getAppShortcutInfo(manager, intent, user, context, null,
1989 cursorIconInfo.iconIndex, titleIndex,
1990 false, useLowResIcon);
1991 } else {
1992 // Don't replace items for other profiles.
1993 itemsToRemove.add(id);
1994 continue;
1995 }
1996 } else if (restored) {
1997 if (user.equals(UserHandleCompat.myUserHandle())) {
1998 Launcher.addDumpLog(TAG,
1999 "constructing info for partially restored package",
2000 true);
2001 info = getRestoredItemInfo(c, titleIndex, intent,
2002 promiseType, itemType, cursorIconInfo, context);
2003 intent = getRestoredItemIntent(c, context, intent);
2004 } else {
2005 // Don't restore items for other profiles.
2006 itemsToRemove.add(id);
2007 continue;
2008 }
2009 } else if (itemType ==
2010 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2011 info = getAppShortcutInfo(manager, intent, user, context, c,
2012 cursorIconInfo.iconIndex, titleIndex,
2013 allowMissingTarget, useLowResIcon);
2014 } else {
2015 info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
2016
2017 // App shortcuts that used to be automatically added to Launcher
2018 // didn't always have the correct intent flags set, so do that
2019 // here
2020 if (intent.getAction() != null &&
2021 intent.getCategories() != null &&
2022 intent.getAction().equals(Intent.ACTION_MAIN) &&
2023 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
2024 intent.addFlags(
2025 Intent.FLAG_ACTIVITY_NEW_TASK |
2026 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
2027 }
2028 }
2029
2030 if (info != null) {
2031 info.id = id;
2032 info.intent = intent;
2033 info.container = container;
2034 info.screenId = c.getInt(screenIndex);
2035 info.cellX = c.getInt(cellXIndex);
2036 info.cellY = c.getInt(cellYIndex);
2037 info.rank = c.getInt(rankIndex);
2038 info.spanX = 1;
2039 info.spanY = 1;
2040 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2041 if (info.promisedIntent != null) {
2042 info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber🔵
2043 }
2044 info.isDisabled = disabledState;
2045 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
2046 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2047 }
2048
2049 // check & update map of what's occupied
2050 if (!checkItemPlacement(occupied, info)) {
2051 itemsToRemove.add(id);
2052 break;
2053 }
2054
2055 if (restored) {
2056 ComponentName cn = info.getTargetComponent();
2057 if (cn != null) {
2058 Integer progress = installingPkgs.get(cn.getPackageName());
2059 if (progress != null) {
2060 info.setInstallProgress(progress);
2061 } else {
2062 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
2063 }
2064 }
2065 }
2066
2067 switch (container) {
2068 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2069 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2070 sBgWorkspaceItems.add(info);
2071 break;
2072 default:
2073 // Item is in a user folder
2074 FolderInfo folderInfo =
2075 findOrMakeFolder(sBgFolders, container);
2076 folderInfo.add(info);
2077 break;
2078 }
2079 sBgItemsIdMap.put(info.id, info);
2080 } else {
2081 throw new RuntimeException("Unexpected null ShortcutInfo");
2082 }
2083 break;
2084
2085 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2086 id = c.getLong(idIndex);
2087 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2088
2089 // Do not trim the folder label, as is was set by the user.
2090 folderInfo.title = c.getString(titleIndex);
2091 folderInfo.id = id;
2092 folderInfo.container = container;
2093 folderInfo.screenId = c.getInt(screenIndex);
2094 folderInfo.cellX = c.getInt(cellXIndex);
2095 folderInfo.cellY = c.getInt(cellYIndex);
2096 folderInfo.spanX = 1;
2097 folderInfo.spanY = 1;
2098 folderInfo.options = c.getInt(optionsIndex);
2099
2100 // check & update map of what's occupied
2101 if (!checkItemPlacement(occupied, folderInfo)) {
2102 itemsToRemove.add(id);
2103 break;
2104 }
2105
2106 switch (container) {
2107 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2108 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2109 sBgWorkspaceItems.add(folderInfo);
2110 break;
2111 }
2112
2113 if (restored) {
2114 // no special handling required for restored folders
2115 restoredRows.add(id);
2116 }
2117
2118 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2119 sBgFolders.put(folderInfo.id, folderInfo);
2120 break;
2121
2122 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2123 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2124 // Read all Launcher-specific widget details
2125 boolean customWidget = itemType ==
2126 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2127
2128 id = c.getLong(idIndex);
2129 int appWidgetId = c.getInt(appWidgetIdIndex);
2130 serialNumber = c.getLong(profileIdIndex);
2131 String savedProvider = c.getString(appWidgetProviderIndex);
2132 id = c.getLong(idIndex);
2133 user = allUsers.get(serialNumber);
2134 if (user == null) {
2135 itemsToRemove.add(id);
2136 continue;
2137 }
2138 <<<<<<< GitAnalyzerPlus_ours
2139
2140 ||||||| GitAnalyzerPlus_base
2141 info.rank = c.getInt(rankIndex);
2142 info.spanX = 1;
2143 info.spanY = 1;
2144 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2145 info.isDisabled = disabledState;
2146 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
2147 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2148 }
2149
2150 // check & update map of what's occupied
2151 if (!checkItemPlacement(occupied, info)) {
2152 itemsToRemove.add(id);
2153 break;
2154 }
2155
2156 switch (container) {
2157 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2158 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2159 sBgWorkspaceItems.add(info);
2160 break;
2161 default:
2162 // Item is in a user folder
2163 FolderInfo folderInfo =
2164 findOrMakeFolder(sBgFolders, container);
2165 folderInfo.add(info);
2166 break;
2167 }
2168 sBgItemsIdMap.put(info.id, info);
2169
2170 // now that we've loaded everthing re-save it with the
2171 // icon in case it disappears somehow.
2172 queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
2173 } else {
2174 throw new RuntimeException("Unexpected null ShortcutInfo");
2175 }
2176 break;
2177
2178 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2179 id = c.getLong(idIndex);
2180 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2181
2182 folderInfo.title = c.getString(titleIndex);
2183 folderInfo.id = id;
2184 container = c.getInt(containerIndex);
2185 folderInfo.container = container;
2186 folderInfo.screenId = c.getInt(screenIndex);
2187 folderInfo.cellX = c.getInt(cellXIndex);
2188 folderInfo.cellY = c.getInt(cellYIndex);
2189 folderInfo.spanX = 1;
2190 folderInfo.spanY = 1;
2191
2192 // check & update map of what's occupied
2193 if (!checkItemPlacement(occupied, folderInfo)) {
2194 itemsToRemove.add(id);
2195 break;
2196 }
2197
2198 switch (container) {
2199 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2200 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2201 sBgWorkspaceItems.add(folderInfo);
2202 break;
2203 }
2204
2205 if (restored) {
2206 // no special handling required for restored folders
2207 restoredRows.add(id);
2208 }
2209
2210 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2211 sBgFolders.put(folderInfo.id, folderInfo);
2212 break;
2213
2214 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2215 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2216 // Read all Launcher-specific widget details
2217 boolean customWidget = itemType ==
2218 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2219
2220 int appWidgetId = c.getInt(appWidgetIdIndex);
2221 serialNumber= c.getLong(profileIdIndex);
2222 user = mUserManager.getUserForSerialNumber(serialNumber);
2223 if (user == null) {
2224 // User has been deleted remove the item.
2225 itemsToRemove.add(id);
2226 continue;
2227 }
2228 String savedProvider = c.getString(appWidgetProviderIndex);
2229 id = c.getLong(idIndex);
2230 =======
2231 String savedProvider = c.getString(appWidgetProviderIndex);
2232 >>>>>>> GitAnalyzerPlus_theirs
2233 final ComponentName component =
2234 ComponentName.unflattenFromString(savedProvider);
2235
2236 final int restoreStatus = c.getInt(restoredIndex);
2237 final boolean isIdValid = (restoreStatus &
2238 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
2239 final boolean wasProviderReady = (restoreStatus &
2240 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
2241
2242 final LauncherAppWidgetProviderInfo provider =
2243 LauncherModel.getProviderInfo(context,
2244 ComponentName.unflattenFromString(savedProvider),
2245 user);
2246
2247 final boolean isProviderReady = isValidProvider(provider);
2248 if (!isSafeMode && !customWidget &&
2249 wasProviderReady && !isProviderReady) {
2250 String log = "Deleting widget that isn't installed anymore: "
2251 + "id=" + id + " appWidgetId=" + appWidgetId;
2252
2253 Log.e(TAG, log);
2254 Launcher.addDumpLog(TAG, log, false);
2255 itemsToRemove.add(id);
2256 } else {
2257 if (isProviderReady) {
2258 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2259 provider.provider);
2260
2261 int status = restoreStatus;
2262 if (!wasProviderReady) {
2263 // If provider was not previously ready, update the
2264 // status and UI flag.
2265
2266 // Id would be valid only if the widget restore broadcast was🔵
2267 if (isIdValid) {
2268 status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
2269 } else {
2270 status &= ~LauncherAppWidgetInfo
2271 .FLAG_PROVIDER_NOT_READY;
2272 }
2273 }
2274 appWidgetInfo.restoreStatus = status;
2275 } else {
2276 Log.v(TAG, "Widget restore pending id=" + id
2277 + " appWidgetId=" + appWidgetId
2278 + " status =" + restoreStatus);
2279 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2280 component);
2281 appWidgetInfo.restoreStatus = restoreStatus;
2282 Integer installProgress = installingPkgs.get(component.getPackage🔵
2283
2284 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) 🔵
2285 // Restore has started once.
2286 } else if (installProgress != null) {
2287 // App restore has started. Update the flag
2288 appWidgetInfo.restoreStatus |=
2289 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2290 } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
2291 Launcher.addDumpLog(TAG,
2292 "Unrestored widget removed: " + component, true);
2293 itemsToRemove.add(id);
2294 continue;
2295 }
2296
2297 appWidgetInfo.installProgress =
2298 installProgress == null ? 0 : installProgress;
2299 }
2300
2301 appWidgetInfo.id = id;
2302 appWidgetInfo.screenId = c.getInt(screenIndex);
2303 appWidgetInfo.cellX = c.getInt(cellXIndex);
2304 appWidgetInfo.cellY = c.getInt(cellYIndex);
2305 appWidgetInfo.spanX = c.getInt(spanXIndex);
2306 appWidgetInfo.spanY = c.getInt(spanYIndex);
2307
2308 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2309 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2310 Log.e(TAG, "Widget found where container != " +
2311 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2312 continue;
2313 }
2314
2315 appWidgetInfo.container = container;
2316 // check & update map of what's occupied
2317 if (!checkItemPlacement(occupied, appWidgetInfo)) {
2318 itemsToRemove.add(id);
2319 break;
2320 }
2321
2322 if (!customWidget) {
2323 String providerName =
2324 appWidgetInfo.providerName.flattenToString();
2325 if (!providerName.equals(savedProvider) ||
2326 (appWidgetInfo.restoreStatus != restoreStatus)) {
2327 ContentValues values = new ContentValues();
2328 values.put(
2329 LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2330 providerName);
2331 values.put(LauncherSettings.Favorites.RESTORED,
2332 appWidgetInfo.restoreStatus);
2333 updateItem(id, values);
2334 }
2335 }
2336 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2337 sBgAppWidgets.add(appWidgetInfo);
2338 }
2339 break;
2340 }
2341 } catch (Exception e) {
2342 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2343 }
2344 }
2345 } finally {
2346 if (c != null) {
2347 c.close();
2348 }
2349 }
2350
2351 // Break early if we've stopped loading
2352 if (mStopped) {
2353 clearSBgDataStructures();
2354 return;
2355 }
2356
2357 if (itemsToRemove.size() > 0) {
2358 // Remove dead items
2359 contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
2360 Utilities.createDbSelectionQuery(
2361 LauncherSettings.Favorites._ID, itemsToRemove), null);
2362 if (DEBUG_LOADERS) {
2363 Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
2364 LauncherSettings.Favorites._ID, itemsToRemove));
2365 }
2366
2367 // Remove any empty folder
2368 for (long folderId : LauncherAppState.getLauncherProvider()
2369 .deleteEmptyFolders()) {
2370 sBgWorkspaceItems.remove(sBgFolders.get(folderId));
2371 sBgFolders.remove(folderId);
2372 sBgItemsIdMap.remove(folderId);
2373 }
2374 }
2375
2376 if (restoredRows.size() > 0) {
2377 // Update restored items that no longer require special handling
2378 ContentValues values = new ContentValues();
2379 values.put(LauncherSettings.Favorites.RESTORED, 0);
2380 contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
2381 Utilities.createDbSelectionQuery(
2382 LauncherSettings.Favorites._ID, restoredRows), null);
2383 }
2384
2385 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
2386 context.registerReceiver(new AppsAvailabilityCheck(),
2387 new IntentFilter(StartupReceiver.SYSTEM_READY),
2388 null, sWorker);
2389 }
2390
2391 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
2392
2393 // Remove any empty screens
2394 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2395 for (ItemInfo item: sBgItemsIdMap) {
2396 long screenId = item.screenId;
2397 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2398 unusedScreens.contains(screenId)) {
2399 unusedScreens.remove(screenId);
2400 }
2401 }
2402
2403 // If there are any empty screens remove them, and update.
2404 if (unusedScreens.size() != 0) {
2405 sBgWorkspaceScreens.removeAll(unusedScreens);
2406 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2407 }
2408
2409 if (DEBUG_LOADERS) {
2410 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2411 Log.d(TAG, "workspace layout: ");
2412 int nScreens = occupied.size();
2413 for (int y = 0; y < countY; y++) {
2414 String line = "";
2415
2416 for (int i = 0; i < nScreens; i++) {
2417 long screenId = occupied.keyAt(i);
2418 if (screenId > 0) {
2419 line += " | ";
2420 }
2421 ItemInfo[][] screen = occupied.valueAt(i);
2422 for (int x = 0; x < countX; x++) {
2423 if (x < screen.length && y < screen[x].length) {
2424 line += (screen[x][y] != null) ? "#" : ".";
2425 } else {
2426 line += "!";
2427 }
2428 }
2429 }
2430 Log.d(TAG, "[ " + line + " ]");
2431 }
2432 }
2433 }
2434 }
2435
2436 /**
2437 * Partially updates the item without any notification. Must be called on the worker thread.
2438 */
2439 private void updateItem(long itemId, ContentValues update) {
2440 mContext.getContentResolver().update(
2441 LauncherSettings.Favorites.CONTENT_URI,
2442 update,
2443 BaseColumns._ID + "= ?",
2444 new String[]{Long.toString(itemId)});
2445 }
2446
2447 /** Filters the set of items who are directly or indirectly (via another container) on the
2448 * specified screen. */
2449 private void filterCurrentWorkspaceItems(long currentScreenId,
2450 ArrayList<ItemInfo> allWorkspaceItems,
2451 ArrayList<ItemInfo> currentScreenItems,
2452 ArrayList<ItemInfo> otherScreenItems) {
2453 // Purge any null ItemInfos
2454 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2455 while (iter.hasNext()) {
2456 ItemInfo i = iter.next();
2457 if (i == null) {
2458 iter.remove();
2459 }
2460 }
2461
2462 // Order the set of items by their containers first, this allows use to walk through the
2463 // list sequentially, build up a list of containers that are in the specified screen,
2464 // as well as all items in those containers.
2465 Set<Long> itemsOnScreen = new HashSet<Long>();
2466 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2467 @Override
2468 public int compare(ItemInfo lhs, ItemInfo rhs) {
2469 return (int) (lhs.container - rhs.container);
2470 }
2471 });
2472 for (ItemInfo info : allWorkspaceItems) {
2473 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2474 if (info.screenId == currentScreenId) {
2475 currentScreenItems.add(info);
2476 itemsOnScreen.add(info.id);
2477 } else {
2478 otherScreenItems.add(info);
2479 }
2480 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2481 currentScreenItems.add(info);
2482 itemsOnScreen.add(info.id);
2483 } else {
2484 if (itemsOnScreen.contains(info.container)) {
2485 currentScreenItems.add(info);
2486 itemsOnScreen.add(info.id);
2487 } else {
2488 otherScreenItems.add(info);
2489 }
2490 }
2491 }
2492 }
2493
2494 /** Filters the set of widgets which are on the specified screen. */
2495 private void filterCurrentAppWidgets(long currentScreenId,
2496 ArrayList<LauncherAppWidgetInfo> appWidgets,
2497 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2498 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2499
2500 for (LauncherAppWidgetInfo widget : appWidgets) {
2501 if (widget == null) continue;
2502 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2503 widget.screenId == currentScreenId) {
2504 currentScreenWidgets.add(widget);
2505 } else {
2506 otherScreenWidgets.add(widget);
2507 }
2508 }
2509 }
2510
2511 /** Filters the set of folders which are on the specified screen. */
2512 private void filterCurrentFolders(long currentScreenId,
2513 LongArrayMap<ItemInfo> itemsIdMap,
2514 LongArrayMap<FolderInfo> folders,
2515 LongArrayMap<FolderInfo> currentScreenFolders,
2516 LongArrayMap<FolderInfo> otherScreenFolders) {
2517
2518 int total = folders.size();
2519 for (int i = 0; i < total; i++) {
2520 long id = folders.keyAt(i);
2521 FolderInfo folder = folders.valueAt(i);
2522
2523 ItemInfo info = itemsIdMap.get(id);
2524 if (info == null || folder == null) continue;
2525 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2526 info.screenId == currentScreenId) {
2527 currentScreenFolders.put(id, folder);
2528 } else {
2529 otherScreenFolders.put(id, folder);
2530 }
2531 }
2532 }
2533
2534 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2535 * right) */
2536 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2537 final LauncherAppState app = LauncherAppState.getInstance();
2538 final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
2539 // XXX: review this
2540 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2541 @Override
2542 public int compare(ItemInfo lhs, ItemInfo rhs) {
2543 int cellCountX = (int) profile.numColumns;
2544 int cellCountY = (int) profile.numRows;
2545 int screenOffset = cellCountX * cellCountY;
2546 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2547 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2548 lhs.cellY * cellCountX + lhs.cellX);
2549 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2550 rhs.cellY * cellCountX + rhs.cellX);
2551 return (int) (lr - rr);
2552 }
2553 });
2554 }
2555
2556 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2557 final ArrayList<Long> orderedScreens) {
2558 final Runnable r = new Runnable() {
2559 @Override
2560 public void run() {
2561 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2562 if (callbacks != null) {
2563 callbacks.bindScreens(orderedScreens);
2564 }
2565 }
2566 };
2567 runOnMainThread(r);
2568 }
2569
2570 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2571 final ArrayList<ItemInfo> workspaceItems,
2572 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2573 final LongArrayMap<FolderInfo> folders,
2574 ArrayList<Runnable> deferredBindRunnables) {
2575
2576 final boolean postOnMainThread = (deferredBindRunnables != null);
2577
2578 // Bind the workspace items
2579 int N = workspaceItems.size();
2580 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2581 final int start = i;
2582 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2583 final Runnable r = new Runnable() {
2584 @Override
2585 public void run() {
2586 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2587 if (callbacks != null) {
2588 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2589 false);
2590 }
2591 }
2592 };
2593 if (postOnMainThread) {
2594 synchronized (deferredBindRunnables) {
2595 deferredBindRunnables.add(r);
2596 }
2597 } else {
2598 runOnMainThread(r);
2599 }
2600 }
2601
2602 // Bind the folders
2603 if (!folders.isEmpty()) {
2604 final Runnable r = new Runnable() {
2605 public void run() {
2606 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2607 if (callbacks != null) {
2608 callbacks.bindFolders(folders);
2609 }
2610 }
2611 };
2612 if (postOnMainThread) {
2613 synchronized (deferredBindRunnables) {
2614 deferredBindRunnables.add(r);
2615 }
2616 } else {
2617 runOnMainThread(r);
2618 }
2619 }
2620
2621 // Bind the widgets, one at a time
2622 N = appWidgets.size();
2623 for (int i = 0; i < N; i++) {
2624 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2625 final Runnable r = new Runnable() {
2626 public void run() {
2627 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2628 if (callbacks != null) {
2629 callbacks.bindAppWidget(widget);
2630 }
2631 }
2632 };
2633 if (postOnMainThread) {
2634 deferredBindRunnables.add(r);
2635 } else {
2636 runOnMainThread(r);
2637 }
2638 }
2639 }
2640
2641 /**
2642 * Binds all loaded data to actual views on the main thread.
2643 */
2644 private void bindWorkspace(int synchronizeBindPage) {
2645 final long t = SystemClock.uptimeMillis();
2646 Runnable r;
2647
2648 // Don't use these two variables in any of the callback runnables.
2649 // Otherwise we hold a reference to them.
2650 final Callbacks oldCallbacks = mCallbacks.get();
2651 if (oldCallbacks == null) {
2652 // This launcher has exited and nobody bothered to tell us. Just bail.
2653 Log.w(TAG, "LoaderTask running with no launcher");
2654 return;
2655 }
2656
2657 // Save a copy of all the bg-thread collections
2658 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2659 ArrayList<LauncherAppWidgetInfo> appWidgets =
2660 new ArrayList<LauncherAppWidgetInfo>();
2661 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2662
2663 final LongArrayMap<FolderInfo> folders;
2664 final LongArrayMap<ItemInfo> itemsIdMap;
2665
2666 synchronized (sBgLock) {
2667 workspaceItems.addAll(sBgWorkspaceItems);
2668 appWidgets.addAll(sBgAppWidgets);
2669 orderedScreenIds.addAll(sBgWorkspaceScreens);
2670
2671 folders = sBgFolders.clone();
2672 itemsIdMap = sBgItemsIdMap.clone();
2673 }
2674
2675 final boolean isLoadingSynchronously =
2676 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2677 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2678 oldCallbacks.getCurrentWorkspaceScreen();
2679 if (currScreen >= orderedScreenIds.size()) {
2680 // There may be no workspace screens (just hotseat items and an empty page).
2681 currScreen = PagedView.INVALID_RESTORE_PAGE;
2682 }
2683 final int currentScreen = currScreen;
2684 final long currentScreenId = currentScreen < 0
2685 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2686
2687 // Load all the items that are on the current page first (and in the process, unbind
2688 // all the existing workspace items before we call startBinding() below.
2689 unbindWorkspaceItemsOnMainThread();
2690
2691 // Separate the items that are on the current screen, and all the other remaining items
2692 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2693 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2694 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2695 new ArrayList<LauncherAppWidgetInfo>();
2696 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2697 new ArrayList<LauncherAppWidgetInfo>();
2698 LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
2699 LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
2700
2701 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2702 otherWorkspaceItems);
2703 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2704 otherAppWidgets);
2705 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2706 otherFolders);
2707 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2708 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2709
2710 // Tell the workspace that we're about to start binding items
2711 r = new Runnable() {
2712 public void run() {
2713 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2714 if (callbacks != null) {
2715 callbacks.startBinding();
2716 }
2717 }
2718 };
2719 runOnMainThread(r);
2720
2721 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2722
2723 // Load items on the current page
2724 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2725 currentFolders, null);
2726 if (isLoadingSynchronously) {
2727 r = new Runnable() {
2728 public void run() {
2729 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2730 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2731 callbacks.onPageBoundSynchronously(currentScreen);
2732 }
2733 }
2734 };
2735 runOnMainThread(r);
2736 }
2737
2738 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2739 // work until after the first render)
2740 synchronized (mDeferredBindRunnables) {
2741 mDeferredBindRunnables.clear();
2742 }
2743 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2744 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2745
2746 // Tell the workspace that we're done binding items
2747 r = new Runnable() {
2748 public void run() {
2749 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2750 if (callbacks != null) {
2751 callbacks.finishBindingItems();
2752 }
2753
2754 // If we're profiling, ensure this is the last thing in the queue.
2755 if (DEBUG_LOADERS) {
2756 Log.d(TAG, "bound workspace in "
2757 + (SystemClock.uptimeMillis()-t) + "ms");
2758 }
2759
2760 mIsLoadingAndBindingWorkspace = false;
2761 }
2762 };
2763 if (isLoadingSynchronously) {
2764 synchronized (mDeferredBindRunnables) {
2765 mDeferredBindRunnables.add(r);
2766 }
2767 } else {
2768 runOnMainThread(r);
2769 }
2770 }
2771
2772 private void loadAndBindAllApps() {
2773 if (DEBUG_LOADERS) {
2774 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2775 }
2776 if (!mAllAppsLoaded) {
2777 loadAllApps();
2778 synchronized (LoaderTask.this) {
2779 if (mStopped) {
2780 return;
2781 }
2782 }
2783 updateIconCache();
2784 synchronized (LoaderTask.this) {
2785 if (mStopped) {
2786 return;
2787 }
2788 mAllAppsLoaded = true;
2789 }
2790 } else {
2791 onlyBindAllApps();
2792 }
2793 }
2794
2795 private void updateIconCache() {
2796 // Ignore packages which have a promise icon.
2797 HashSet<String> packagesToIgnore = new HashSet<>();
2798 synchronized (sBgLock) {
2799 for (ItemInfo info : sBgItemsIdMap) {
2800 if (info instanceof ShortcutInfo) {
2801 ShortcutInfo si = (ShortcutInfo) info;
2802 if (si.isPromise() && si.getTargetComponent() != null) {
2803 packagesToIgnore.add(si.getTargetComponent().getPackageName());
2804 }
2805 } else if (info instanceof LauncherAppWidgetInfo) {
2806 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
2807 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2808 packagesToIgnore.add(lawi.providerName.getPackageName());
2809 }
2810 }
2811 }
2812 }
2813 mIconCache.updateDbIcons(packagesToIgnore);
2814 }
2815
2816 private void onlyBindAllApps() {
2817 final Callbacks oldCallbacks = mCallbacks.get();
2818 if (oldCallbacks == null) {
2819 // This launcher has exited and nobody bothered to tell us. Just bail.
2820 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2821 return;
2822 }
2823
2824 // shallow copy
2825 @SuppressWarnings("unchecked")
2826 final ArrayList<AppInfo> list
2827 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2828 final WidgetsModel widgetList = mBgWidgetsModel.clone();
2829 Runnable r = new Runnable() {
2830 public void run() {
2831 final long t = SystemClock.uptimeMillis();
2832 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2833 if (callbacks != null) {
2834 callbacks.bindAllApplications(list);
2835 callbacks.bindAllPackages(widgetList);
2836 }
2837 if (DEBUG_LOADERS) {
2838 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2839 + (SystemClock.uptimeMillis()-t) + "ms");
2840 }
2841 }
2842 };
2843 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2844 if (isRunningOnMainThread) {
2845 r.run();
2846 } else {
2847 mHandler.post(r);
2848 }
2849 }
2850
2851 private void loadAllApps() {
2852 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2853
2854 final Callbacks oldCallbacks = mCallbacks.get();
2855 if (oldCallbacks == null) {
2856 // This launcher has exited and nobody bothered to tell us. Just bail.
2857 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2858 return;
2859 }
2860
2861 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2862
2863 // Clear the list of apps
2864 mBgAllAppsList.clear();
2865 for (UserHandleCompat user : profiles) {
2866 // Query for the set of apps
2867 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2868 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2869 if (DEBUG_LOADERS) {
2870 Log.d(TAG, "getActivityList took "
2871 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2872 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2873 }
2874 // Fail if we don't have any apps
2875 // TODO: Fix this. Only fail for the current user.
2876 if (apps == null || apps.isEmpty()) {
2877 return;
2878 }
2879
2880 // Create the ApplicationInfos
2881 for (int i = 0; i < apps.size(); i++) {
2882 LauncherActivityInfoCompat app = apps.get(i);
2883 // This builds the icon bitmaps.
2884 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
2885 }
2886
2887 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2888 if (heuristic != null) {
2889 runAfterBindCompletes(new Runnable() {
2890
2891 @Override
2892 public void run() {
2893 heuristic.processUserApps(apps);
2894 }
2895 });
2896 }
2897 }
2898 // Huh? Shouldn't this be inside the Runnable below?
2899 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2900 mBgAllAppsList.added = new ArrayList<AppInfo>();
2901
2902 // Post callback on main thread
2903 mHandler.post(new Runnable() {
2904 public void run() {
2905
2906 final long bindTime = SystemClock.uptimeMillis();
2907 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2908 if (callbacks != null) {
2909 callbacks.bindAllApplications(added);
2910 if (DEBUG_LOADERS) {
2911 Log.d(TAG, "bound " + added.size() + " apps in "
2912 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2913 }
2914 } else {
2915 Log.i(TAG, "not binding apps: no Launcher activity");
2916 }
2917 }
2918 });
2919 // Cleanup any data stored for a deleted user.
2920 ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2921
2922 loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
2923 true /* refresh */);
2924 if (DEBUG_LOADERS) {
2925 Log.d(TAG, "Icons processed in "
2926 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2927 }
2928 }
2929
2930 public void dumpState() {
2931 synchronized (sBgLock) {
2932 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2933 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2934 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2935 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2936 }
2937 }
2938 }
2939
2940 /**
2941 * Called when the icons for packages have been updated in the icon cache.
2942 */
2943 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
2944 final Callbacks callbacks = getCallback();
2945 final ArrayList<AppInfo> updatedApps = new ArrayList<>();
2946 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
2947
2948 // If any package icon has changed (app was updated while launcher was dead),
2949 // update the corresponding shortcuts.
2950 synchronized (sBgLock) {
2951 for (ItemInfo info : sBgItemsIdMap) {
2952 if (info instanceof ShortcutInfo && user.equals(info.user)
2953 && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2954 ShortcutInfo si = (ShortcutInfo) info;
2955 ComponentName cn = si.getTargetComponent();
2956 if (cn != null && updatedPackages.contains(cn.getPackageName())) {
2957 si.updateIcon(mIconCache);
2958 updatedShortcuts.add(si);
2959 }
2960 }
2961 }
2962 mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
2963 }
2964
2965 if (!updatedShortcuts.isEmpty()) {
2966 final UserHandleCompat userFinal = user;
2967 mHandler.post(new Runnable() {
2968
2969 public void run() {
2970 Callbacks cb = getCallback();
2971 if (cb != null && callbacks == cb) {
2972 cb.bindShortcutsChanged(updatedShortcuts,
2973 new ArrayList<ShortcutInfo>(), userFinal);
2974 }
2975 }
2976 });
2977 }
2978
2979 if (!updatedApps.isEmpty()) {
2980 mHandler.post(new Runnable() {
2981
2982 public void run() {
2983 Callbacks cb = getCallback();
2984 if (cb != null && callbacks == cb) {
2985 cb.bindAppsUpdated(updatedApps);
2986 }
2987 }
2988 });
2989 }
2990
2991 // Reload widget list. No need to refresh, as we only want to update the icons and labels.
2992 loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, false);
2993 }
2994
2995 void enqueuePackageUpdated(PackageUpdatedTask task) {
2996 sWorker.post(task);
2997 }
2998
2999 @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
3000
3001 @Override
3002 public void onReceive(Context context, Intent intent) {
3003 synchronized (sBgLock) {
3004 final LauncherAppsCompat launcherApps = LauncherAppsCompat
3005 .getInstance(mApp.getContext());
3006 final PackageManager manager = context.getPackageManager();
3007 final ArrayList<String> packagesRemoved = new ArrayList<String>();
3008 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
3009 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
3010 UserHandleCompat user = entry.getKey();
3011 packagesRemoved.clear();
3012 packagesUnavailable.clear();
3013 for (String pkg : entry.getValue()) {
3014 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
3015 boolean packageOnSdcard = launcherApps.isAppEnabled(
3016 manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
3017 if (packageOnSdcard) {
3018 Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
3019 packagesUnavailable.add(pkg);
3020 } else {
3021 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
3022 packagesRemoved.add(pkg);
3023 }
3024 }
3025 }
3026 if (!packagesRemoved.isEmpty()) {
3027 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
3028 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
3029 }
3030 if (!packagesUnavailable.isEmpty()) {
3031 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
3032 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user🔵
3033 }
3034 }
3035 sPendingPackages.clear();
3036 }
3037 }
3038 }
3039
3040 private class PackageUpdatedTask implements Runnable {
3041 int mOp;
3042 String[] mPackages;
3043 UserHandleCompat mUser;
3044
3045 public static final int OP_NONE = 0;
3046 public static final int OP_ADD = 1;
3047 public static final int OP_UPDATE = 2;
3048 public static final int OP_REMOVE = 3; // uninstlled
3049 public static final int OP_UNAVAILABLE = 4; // external media unmounted
3050
3051
3052 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
3053 mOp = op;
3054 mPackages = packages;
3055 mUser = user;
3056 }
3057
3058 public void run() {
3059 if (!mHasLoaderCompletedOnce) {
3060 // Loader has not yet run.
3061 return;
3062 }
3063 final Context context = mApp.getContext();
3064
3065 final String[] packages = mPackages;
3066 final int N = packages.length;
3067 switch (mOp) {
3068 case OP_ADD: {
3069 for (int i=0; i<N; i++) {
3070 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
3071 mIconCache.updateIconsForPkg(packages[i], mUser);
3072 mBgAllAppsList.addPackage(context, packages[i], mUser);
3073 }
3074
3075 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3076 if (heuristic != null) {
3077 heuristic.processPackageAdd(mPackages);
3078 }
3079 break;
3080 }
3081 case OP_UPDATE:
3082 for (int i=0; i<N; i++) {
3083 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
3084 mIconCache.updateIconsForPkg(packages[i], mUser);
3085 mBgAllAppsList.updatePackage(context, packages[i], mUser);
3086 mApp.getWidgetCache().removePackage(packages[i], mUser);
3087 }
3088 break;
3089 case OP_REMOVE: {
3090 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3091 if (heuristic != null) {
3092 heuristic.processPackageRemoved(mPackages);
3093 }
3094 for (int i=0; i<N; i++) {
3095 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3096 mIconCache.removeIconsForPkg(packages[i], mUser);
3097 }
3098 // Fall through
3099 }
3100 case OP_UNAVAILABLE:
3101 for (int i=0; i<N; i++) {
3102 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3103 mBgAllAppsList.removePackage(packages[i], mUser);
3104 mApp.getWidgetCache().removePackage(packages[i], mUser);
3105 }
3106 break;
3107 }
3108
3109 ArrayList<AppInfo> added = null;
3110 ArrayList<AppInfo> modified = null;
3111 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
3112
3113 if (mBgAllAppsList.added.size() > 0) {
3114 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
3115 mBgAllAppsList.added.clear();
3116 }
3117 if (mBgAllAppsList.modified.size() > 0) {
3118 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
3119 mBgAllAppsList.modified.clear();
3120 }
3121 if (mBgAllAppsList.removed.size() > 0) {
3122 removedApps.addAll(mBgAllAppsList.removed);
3123 mBgAllAppsList.removed.clear();
3124 }
3125
3126 final Callbacks callbacks = getCallback();
3127 if (callbacks == null) {
3128 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
3129 return;
3130 }
3131
3132 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps =
3133 new HashMap<ComponentName, AppInfo>();
3134
3135 if (added != null) {
3136 addAppsToAllApps(context, added);
3137 for (AppInfo ai : added) {
3138 addedOrUpdatedApps.put(ai.componentName, ai);
3139 }
3140 }
3141
3142 if (modified != null) {
3143 final ArrayList<AppInfo> modifiedFinal = modified;
3144 for (AppInfo ai : modified) {
3145 addedOrUpdatedApps.put(ai.componentName, ai);
3146 }
3147
3148 mHandler.post(new Runnable() {
3149 public void run() {
3150 Callbacks cb = getCallback();
3151 if (callbacks == cb && cb != null) {
3152 callbacks.bindAppsUpdated(modifiedFinal);
3153 }
3154 }
3155 });
3156 }
3157
3158 // Update shortcut infos
3159 if (mOp == OP_ADD || mOp == OP_UPDATE) {
3160 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
3161 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
3162 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
3163
3164 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
3165 synchronized (sBgLock) {
3166 for (ItemInfo info : sBgItemsIdMap) {
3167 if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
3168 ShortcutInfo si = (ShortcutInfo) info;
3169 boolean infoUpdated = false;
3170 boolean shortcutUpdated = false;
3171
3172 // Update shortcuts which use iconResource.
3173 if ((si.iconResource != null)
3174 && packageSet.contains(si.iconResource.packageName)) {
3175 Bitmap icon = Utilities.createIconBitmap(
3176 si.iconResource.packageName,
3177 si.iconResource.resourceName, context);
3178 if (icon != null) {
3179 si.setIcon(icon);
3180 si.usingFallbackIcon = false;
3181 infoUpdated = true;
3182 }
3183 }
3184
3185 ComponentName cn = si.getTargetComponent();
3186 if (cn != null && packageSet.contains(cn.getPackageName())) {
3187 AppInfo appInfo = addedOrUpdatedApps.get(cn);
3188
3189 if (si.isPromise()) {
3190 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
3191 // Auto install icon
3192 PackageManager pm = context.getPackageManager();
3193 ResolveInfo matched = pm.resolveActivity(
3194 new Intent(Intent.ACTION_MAIN)
3195 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
3196 PackageManager.MATCH_DEFAULT_ONLY);
3197 if (matched == null) {
3198 // Try to find the best match activity.
3199 Intent intent = pm.getLaunchIntentForPackage(
3200 cn.getPackageName());
3201 if (intent != null) {
3202 cn = intent.getComponent();
3203 appInfo = addedOrUpdatedApps.get(cn);
3204 }
3205
3206 if ((intent == null) || (appInfo == null)) {
3207 removedShortcuts.add(si);
3208 continue;
3209 }
3210 si.promisedIntent = intent;
3211 }
3212 }
3213
3214 // Restore the shortcut.
3215 if (appInfo != null) {
3216 si.flags = appInfo.flags;
3217 }
3218
3219 si.intent = si.promisedIntent;
3220 si.promisedIntent = null;
3221 si.status = ShortcutInfo.DEFAULT;
3222 infoUpdated = true;
3223 si.updateIcon(mIconCache);
3224 }
3225
3226 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
3227 && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATIO🔵
3228 si.updateIcon(mIconCache);
3229 si.title = Utilities.trim(appInfo.title);
3230 si.contentDescription = appInfo.contentDescription;
3231 infoUpdated = true;
3232 }
3233
3234 if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
3235 // Since package was just updated, the target must be available now.
3236 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3237 shortcutUpdated = true;
3238 }
3239 }
3240
3241 if (infoUpdated || shortcutUpdated) {
3242 updatedShortcuts.add(si);
3243 }
3244 if (infoUpdated) {
3245 updateItemInDatabase(context, si);
3246 }
3247 } else if (info instanceof LauncherAppWidgetInfo) {
3248 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
3249 if (mUser.equals(widgetInfo.user)
3250 && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_🔵
3251 && packageSet.contains(widgetInfo.providerName.getPackageName())) {
3252 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
3253 widgets.add(widgetInfo);
3254 updateItemInDatabase(context, widgetInfo);
3255 }
3256 }
3257 }
3258 }
3259
3260 if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
3261 mHandler.post(new Runnable() {
3262
3263 public void run() {
3264 Callbacks cb = getCallback();
3265 if (callbacks == cb && cb != null) {
3266 callbacks.bindShortcutsChanged(
3267 updatedShortcuts, removedShortcuts, mUser);
3268 }
3269 }
3270 });
3271 if (!removedShortcuts.isEmpty()) {
3272 deleteItemsFromDatabase(context, removedShortcuts);
3273 }
3274 }
3275 if (!widgets.isEmpty()) {
3276 mHandler.post(new Runnable() {
3277 public void run() {
3278 Callbacks cb = getCallback();
3279 if (callbacks == cb && cb != null) {
3280 callbacks.bindWidgetsRestored(widgets);
3281 }
3282 }
3283 });
3284 }
3285 }
3286
3287 final ArrayList<String> removedPackageNames =
3288 new ArrayList<String>();
3289 if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) {
3290 // Mark all packages in the broadcast to be removed
3291 removedPackageNames.addAll(Arrays.asList(packages));
3292 } else if (mOp == OP_UPDATE) {
3293 // Mark disabled packages in the broadcast to be removed
3294 for (int i=0; i<N; i++) {
3295 if (isPackageDisabled(context, packages[i], mUser)) {
3296 removedPackageNames.add(packages[i]);
3297 }
3298 }
3299 }
3300
3301 if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
3302 final int removeReason;
3303 if (mOp == OP_UNAVAILABLE) {
3304 removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3305 } else {
3306 // Remove all the components associated with this package
3307 for (String pn : removedPackageNames) {
3308 deletePackageFromDatabase(context, pn, mUser);
3309 }
3310 // Remove all the specific components
3311 for (AppInfo a : removedApps) {
3312 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
3313 deleteItemsFromDatabase(context, infos);
3314 }
3315 removeReason = 0;
3316 }
3317
3318 // Remove any queued items from the install queue
3319 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
3320 // Call the components-removed callback
3321 mHandler.post(new Runnable() {
3322 public void run() {
3323 Callbacks cb = getCallback();
3324 if (callbacks == cb && cb != null) {
3325 callbacks.bindComponentsRemoved(
3326 removedPackageNames, removedApps, mUser, removeReason);
3327 }
3328 }
3329 });
3330 }
3331
3332 // onProvidersChanged method (API >= 17) already refreshed the widget list
3333 loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
3334
3335 // Write all the logs to disk
3336 mHandler.post(new Runnable() {
3337 public void run() {
3338 Callbacks cb = getCallback();
3339 if (callbacks == cb && cb != null) {
3340 callbacks.dumpLogsToLocalData();
3341 }
3342 }
3343 });
3344 }
3345 }
3346
3347 public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context,
3348 boolean refresh) {
3349 ArrayList<LauncherAppWidgetProviderInfo> results =
3350 new ArrayList<LauncherAppWidgetProviderInfo>();
3351 try {
3352 synchronized (sBgLock) {
3353 if (sBgWidgetProviders == null || refresh) {
3354 HashMap<ComponentKey, LauncherAppWidgetProviderInfo> tmpWidgetProviders
3355 = new HashMap<>();
3356 AppWidgetManagerCompat wm = AppWidgetManagerCompat.getInstance(context);
3357 LauncherAppWidgetProviderInfo info;
3358
3359 List<AppWidgetProviderInfo> widgets = wm.getAllProviders();
3360 for (AppWidgetProviderInfo pInfo : widgets) {
3361 info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
3362 UserHandleCompat user = wm.getUser(info);
3363 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3364 }
3365
3366 Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
3367 for (CustomAppWidget widget : customWidgets) {
3368 info = new LauncherAppWidgetProviderInfo(context, widget);
3369 UserHandleCompat user = wm.getUser(info);
3370 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3371 }
3372 // Replace the global list at the very end, so that if there is an exception,
3373 // previously loaded provider list is used.
3374 sBgWidgetProviders = tmpWidgetProviders;
3375 }
3376 results.addAll(sBgWidgetProviders.values());
3377 return results;
3378 }
3379 } catch (Exception e) {
3380 if (e.getCause() instanceof TransactionTooLargeException) {
3381 // the returned value may be incomplete and will not be refreshed until the next
3382 // time Launcher starts.
3383 // TODO: after figuring out a repro step, introduce a dirty bit to check when
3384 // onResume is called to refresh the widget provider list.
3385 synchronized (sBgLock) {
3386 if (sBgWidgetProviders != null) {
3387 results.addAll(sBgWidgetProviders.values());
3388 }
3389 return results;
3390 }
3391 } else {
3392 throw e;
3393 }
3394 }
3395 }
3396
3397 public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name,
3398 UserHandleCompat user) {
3399 synchronized (sBgLock) {
3400 if (sBgWidgetProviders == null) {
3401 getWidgetProviders(ctx, false /* refresh */);
3402 }
3403 return sBgWidgetProviders.get(new ComponentKey(name, user));
3404 }
3405 }
3406
3407 public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks,
3408 final boolean refresh) {
3409
3410 runOnWorkerThread(new Runnable() {
3411 @Override
3412 public void run() {
3413 updateWidgetsModel(context, refresh);
3414 final WidgetsModel model = mBgWidgetsModel.clone();
3415
3416 mHandler.post(new Runnable() {
3417 @Override
3418 public void run() {
3419 Callbacks cb = getCallback();
3420 if (callbacks == cb && cb != null) {
3421 callbacks.bindAllPackages(model);
3422 }
3423 }
3424 });
3425 // update the Widget entries inside DB on the worker thread.
3426 LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
3427 model.getRawList());
3428 }
3429 });
3430 }
3431
3432 /**
3433 * Returns a list of ResolveInfos/AppWidgetInfos.
3434 *
3435 * @see #loadAndBindWidgetsAndShortcuts
3436 */
3437 @Thunk void updateWidgetsModel(Context context, boolean refresh) {
3438 PackageManager packageManager = context.getPackageManager();
3439 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3440 widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
3441 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3442 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3443 mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
3444 }
3445
3446 @Thunk static boolean isPackageDisabled(Context context, String packageName,
3447 UserHandleCompat user) {
3448 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3449 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3450 }
3451
3452 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3453 UserHandleCompat user) {
3454 if (cn == null) {
3455 return false;
3456 }
3457 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3458 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3459 return false;
3460 }
3461 return launcherApps.isActivityEnabledForProfile(cn, user);
3462 }
3463
3464 public static boolean isValidPackage(Context context, String packageName,
3465 UserHandleCompat user) {
3466 if (packageName == null) {
3467 return false;
3468 }
3469 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3470 return launcherApps.isPackageEnabledForProfile(packageName, user);
3471 }
3472
3473 /**
3474 * Make an ShortcutInfo object for a restored application or shortcut item that points
3475 * to a package that is not yet installed on the system.
3476 */
3477 public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent,
3478 int promiseType, int itemType, CursorIconInfo iconInfo, Context context) {
3479 final ShortcutInfo info = new ShortcutInfo();
3480 info.user = UserHandleCompat.myUserHandle();
3481
3482 Bitmap icon = iconInfo.loadIcon(c, info, context);
3483 // the fallback icon
3484 if (icon == null) {
3485 mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
3486 } else {
3487 info.setIcon(icon);
3488 }
3489
3490 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3491 String title = (c != null) ? c.getString(titleIndex) : null;
3492 if (!TextUtils.isEmpty(title)) {
3493 info.title = Utilities.trim(title);
3494 }
3495 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3496 if (TextUtils.isEmpty(info.title)) {
3497 info.title = (c != null) ? Utilities.trim(c.getString(titleIndex)) : "";
3498 }
3499 } else {
3500 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3501 }
3502
3503 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3504 info.itemType = itemType;
3505 info.promisedIntent = intent;
3506 info.status = promiseType;
3507 return info;
3508 }
3509
3510 /**
3511 * Make an Intent object for a restored application or shortcut item that points
3512 * to the market page for the item.
3513 */
3514 @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3515 ComponentName componentName = intent.getComponent();
3516 return getMarketIntent(componentName.getPackageName());
3517 }
3518
3519 static Intent getMarketIntent(String packageName) {
3520 return new Intent(Intent.ACTION_VIEW)
3521 .setData(new Uri.Builder()
3522 .scheme("market")
3523 .authority("details")
3524 .appendQueryParameter("id", packageName)
3525 .build());
3526 }
3527
3528 /**
3529 * Make an ShortcutInfo object for a shortcut that is an application.
3530 *
3531 * If c is not null, then it will be used to fill in missing data like the title and icon.
3532 */
3533 public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent,
3534 UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
3535 boolean allowMissingTarget, boolean useLowResIcon) {
3536 if (user == null) {
3537 Log.d(TAG, "Null user found in getShortcutInfo");
3538 return null;
3539 }
3540
3541 ComponentName componentName = intent.getComponent();
3542 if (componentName == null) {
3543 Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
3544 return null;
3545 }
3546
3547 Intent newIntent = new Intent(intent.getAction(), null);
3548 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3549 newIntent.setComponent(componentName);
3550 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3551 if ((lai == null) && !allowMissingTarget) {
3552 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3553 return null;
3554 }
3555
3556 final ShortcutInfo info = new ShortcutInfo();
3557 mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
3558 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
3559 Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
3560 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3561 }
3562
3563 // from the db
3564 if (TextUtils.isEmpty(info.title) && c != null) {
3565 info.title = Utilities.trim(c.getString(titleIndex));
3566 }
3567
3568 // fall back to the class name of the activity
3569 if (info.title == null) {
3570 info.title = componentName.getClassName();
3571 }
3572
3573 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3574 info.user = user;
3575 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3576 if (lai != null) {
3577 info.flags = AppInfo.initFlags(lai);
3578 }
3579 return info;
3580 }
3581
3582 static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
3583 ItemInfoFilter f) {
3584 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3585 for (ItemInfo i : infos) {
3586 if (i instanceof ShortcutInfo) {
3587 ShortcutInfo info = (ShortcutInfo) i;
3588 ComponentName cn = info.getTargetComponent();
3589 if (cn != null && f.filterItem(null, info, cn)) {
3590 filtered.add(info);
3591 }
3592 } else if (i instanceof FolderInfo) {
3593 FolderInfo info = (FolderInfo) i;
3594 for (ShortcutInfo s : info.contents) {
3595 ComponentName cn = s.getTargetComponent();
3596 if (cn != null && f.filterItem(info, s, cn)) {
3597 filtered.add(s);
3598 }
3599 }
3600 } else if (i instanceof LauncherAppWidgetInfo) {
3601 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3602 ComponentName cn = info.providerName;
3603 if (cn != null && f.filterItem(null, info, cn)) {
3604 filtered.add(info);
3605 }
3606 }
3607 }
3608 return new ArrayList<ItemInfo>(filtered);
3609 }
3610
3611 @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
3612 final UserHandleCompat user) {
3613 ItemInfoFilter filter = new ItemInfoFilter() {
3614 @Override
3615 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3616 if (info.user == null) {
3617 return cn.equals(cname);
3618 } else {
3619 return cn.equals(cname) && info.user.equals(user);
3620 }
3621 }
3622 };
3623 return filterItemInfos(sBgItemsIdMap, filter);
3624 }
3625
3626 /**
3627 * Make an ShortcutInfo object for a shortcut that isn't an application.
3628 */
3629 @Thunk ShortcutInfo getShortcutInfo(Cursor c, Context context,
3630 int titleIndex, CursorIconInfo iconInfo) {
3631 final ShortcutInfo info = new ShortcutInfo();
3632 // Non-app shortcuts are only supported for current user.
3633 info.user = UserHandleCompat.myUserHandle();
3634 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3635
3636 // TODO: If there's an explicit component and we can't install that, delete it.
3637
3638 info.title = Utilities.trim(c.getString(titleIndex));
3639
3640 Bitmap icon = iconInfo.loadIcon(c, info, context);
3641 // the fallback icon
3642 if (icon == null) {
3643 icon = mIconCache.getDefaultIcon(info.user);
3644 info.usingFallbackIcon = true;
3645 }
3646 info.setIcon(icon);
3647 return info;
3648 }
3649
3650 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3651 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3652 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3653 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3654
3655 if (intent == null) {
3656 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3657 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3658 return null;
3659 }
3660
3661 Bitmap icon = null;
3662 boolean customIcon = false;
3663 ShortcutIconResource iconResource = null;
3664
3665 if (bitmap instanceof Bitmap) {
3666 icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
3667 customIcon = true;
3668 } else {
3669 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3670 if (extra instanceof ShortcutIconResource) {
3671 iconResource = (ShortcutIconResource) extra;
3672 icon = Utilities.createIconBitmap(iconResource.packageName,
3673 iconResource.resourceName, context);
3674 }
3675 }
3676
3677 final ShortcutInfo info = new ShortcutInfo();
3678
3679 // Only support intents for current user for now. Intents sent from other
3680 // users wouldn't get here without intent forwarding anyway.
3681 info.user = UserHandleCompat.myUserHandle();
3682 if (icon == null) {
3683 icon = mIconCache.getDefaultIcon(info.user);
3684 info.usingFallbackIcon = true;
3685 }
3686 info.setIcon(icon);
3687
3688 info.title = Utilities.trim(name);
3689 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3690 info.intent = intent;
3691 info.customIcon = customIcon;
3692 info.iconResource = iconResource;
3693
3694 return info;
3695 }
3696
3697 /**
3698 * Return an existing FolderInfo object if we have encountered this ID previously,
3699 * or make a new one.
3700 */
3701 @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
3702 // See if a placeholder was created for us already
3703 FolderInfo folderInfo = folders.get(id);
3704 if (folderInfo == null) {
3705 // No placeholder -- create a new instance
3706 folderInfo = new FolderInfo();
3707 folders.put(id, folderInfo);
3708 }
3709 return folderInfo;
3710 }
3711
3712
3713 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3714 return (provider != null) && (provider.provider != null)
3715 && (provider.provider.getPackageName() != null);
3716 }
3717
3718 public void dumpState() {
3719 Log.d(TAG, "mCallbacks=" + mCallbacks);
3720 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3721 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3722 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3723 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3724 if (mLoaderTask != null) {
3725 mLoaderTask.dumpState();
3726 } else {
3727 Log.d(TAG, "mLoaderTask=null");
3728 }
3729 }
3730
3731 public Callbacks getCallback() {
3732 return mCallbacks != null ? mCallbacks.get() : null;
3733 }
3734
3735 /**
3736 * @return {@link FolderInfo} if its already loaded.
3737 */
3738 public FolderInfo findFolderById(Long folderId) {
3739 synchronized (sBgLock) {
3740 return sBgFolders.get(folderId);
3741 }
3742 }
3743
3744 /**
3745 * @return the looper for the worker thread which can be used to start background tasks.
3746 */
3747 public static Looper getWorkerLooper() {
3748 return sWorkerThread.getLooper();
3749 }
3750 } |
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.launcher3;
18
19 import android.app.SearchManager;
20 import android.appwidget.AppWidgetProviderInfo;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.ContentProviderOperation;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.Intent.ShortcutIconResource;
29 import android.content.IntentFilter;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ProviderInfo;
32 import android.content.pm.ResolveInfo;
33 import android.database.Cursor;
34 import android.graphics.Bitmap;
35 import android.net.Uri;
36 import android.os.Build;
37 import android.os.Environment;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.os.Looper;
41 import android.os.Parcelable;
42 import android.os.Process;
43 import android.os.SystemClock;
44 import android.os.TransactionTooLargeException;
45 import android.provider.BaseColumns;
46 import android.text.TextUtils;
47 import android.util.Log;
48 import android.util.LongSparseArray;
49 import android.util.Pair;
50
51 import com.android.launcher3.compat.AppWidgetManagerCompat;
52 import com.android.launcher3.compat.LauncherActivityInfoCompat;
53 import com.android.launcher3.compat.LauncherAppsCompat;
54 import com.android.launcher3.compat.PackageInstallerCompat;
55 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
56 import com.android.launcher3.compat.UserHandleCompat;
57 import com.android.launcher3.compat.UserManagerCompat;
58 import com.android.launcher3.model.WidgetsModel;
59 import com.android.launcher3.util.ComponentKey;
60 import com.android.launcher3.util.CursorIconInfo;
61 import com.android.launcher3.util.LongArrayMap;
62 import com.android.launcher3.util.ManagedProfileHeuristic;
63 import com.android.launcher3.util.Thunk;
64
65 import java.lang.ref.WeakReference;
66 import java.net.URISyntaxException;
67 import java.security.InvalidParameterException;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.Collection;
71 import java.util.Collections;
72 import java.util.Comparator;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.Iterator;
76 import java.util.List;
77 import java.util.Map.Entry;
78 import java.util.Set;
79
80 /**
81 * Maintains in-memory state of the Launcher. It is expected that there should be only one
82 * LauncherModel object held in a static. Also provide APIs for updating the database state
83 * for the Launcher.
84 */
85 public class LauncherModel extends BroadcastReceiver
86 implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
87 static final boolean DEBUG_LOADERS = false;
88 private static final boolean DEBUG_RECEIVER = false;
89 private static final boolean REMOVE_UNRESTORED_ICONS = true;
90
91 static final String TAG = "Launcher.Model";
92
93 public static final int LOADER_FLAG_NONE = 0;
94 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
95 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
96
97 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
98 private static final long INVALID_SCREEN_ID = -1L;
99
100 @Thunk final boolean mAppsCanBeOnRemoveableStorage;
101 private final boolean mOldContentProviderExists;
102
103 @Thunk final LauncherAppState mApp;
104 @Thunk final Object mLock = new Object();
105 @Thunk DeferredHandler mHandler = new DeferredHandler();
106 @Thunk LoaderTask mLoaderTask;
107 @Thunk boolean mIsLoaderTaskRunning;
108 @Thunk boolean mHasLoaderCompletedOnce;
109
110 private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
111
112 @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
113 static {
114 sWorkerThread.start();
115 }
116 @Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());
117
118 // We start off with everything not loaded. After that, we assume that
119 // our monitoring of the package manager provides all updates and we never
120 // need to do a requery. These are only ever touched from the loader thread.
121 @Thunk boolean mWorkspaceLoaded;
122 @Thunk boolean mAllAppsLoaded;
123
124 // When we are loading pages synchronously, we can't just post the binding of items on the side
125 // pages as this delays the rotation process. Instead, we wait for a callback from the first
126 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
127 // a normal load, we also clear this set of Runnables.
128 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
129
130 /**
131 * Set of runnables to be called on the background thread after the workspace binding
132 * is complete.
133 */
134 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
135
136 @Thunk WeakReference<Callbacks> mCallbacks;
137
138 // < only access in worker thread >
139 AllAppsList mBgAllAppsList;
140 // Entire list of widgets.
141 WidgetsModel mBgWidgetsModel;
142
143 // The lock that must be acquired before referencing any static bg data structures. Unlike
144 // other locks, this one can generally be held long-term because we never expect any of these
145 // static data structures to be referenced outside of the worker thread except on the first
146 // load after configuration change.
147 static final Object sBgLock = new Object();
148
149 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
150 // LauncherModel to their ids
151 static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
152
153 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
154 // created by LauncherModel that are directly on the home screen (however, no widgets or
155 // shortcuts within folders).
156 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
157
158 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
159 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
160 new ArrayList<LauncherAppWidgetInfo>();
161
162 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
163 static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
164
165 // sBgWorkspaceScreens is the ordered set of workspace screens.
166 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
167
168 // sBgWidgetProviders is the set of widget providers including custom internal widgets
169 public static HashMap<ComponentKey, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
170
171 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
172 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages =
173 new HashMap<UserHandleCompat, HashSet<String>>();
174
175 // </ only access in worker thread >
176
177 @Thunk IconCache mIconCache;
178
179 @Thunk final LauncherAppsCompat mLauncherApps;
180 @Thunk final UserManagerCompat mUserManager;
181
182 public interface Callbacks {
183 public boolean setLoadOnResume();
184 public int getCurrentWorkspaceScreen();
185 public void startBinding();
186 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
187 boolean forceAnimateIcons);
188 public void bindScreens(ArrayList<Long> orderedScreenIds);
189 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
190 public void bindFolders(LongArrayMap<FolderInfo> folders);
191 public void finishBindingItems();
192 public void bindAppWidget(LauncherAppWidgetInfo info);
193 public void bindAllApplications(ArrayList<AppInfo> apps);
194 public void bindAppsAdded(ArrayList<Long> newScreens,
195 ArrayList<ItemInfo> addNotAnimated,
196 ArrayList<ItemInfo> addAnimated,
197 ArrayList<AppInfo> addedApps);
198 public void bindAppsUpdated(ArrayList<AppInfo> apps);
199 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
200 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
201 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
202 public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
203 public void bindComponentsRemoved(ArrayList<String> packageNames,
204 ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
205 public void bindAllPackages(WidgetsModel model);
206 public void bindSearchablesChanged();
207 public boolean isAllAppsButtonRank(int rank);
208 public void onPageBoundSynchronously(int page);
209 public void dumpLogsToLocalData();
210 }
211
212 public interface ItemInfoFilter {
213 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
214 }
215
216 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
217 Context context = app.getContext();
218
219 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
220 String oldProvider = context.getString(R.string.old_launcher_provider_uri);
221 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
222 // resource string.
223 String redirectAuthority = Uri.parse(oldProvider).getAuthority();
224 ProviderInfo providerInfo =
225 context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
226 ProviderInfo redirectProvider =
227 context.getPackageManager().resolveContentProvider(redirectAuthority, 0);
228
229 Log.d(TAG, "Old launcher provider: " + oldProvider);
230 mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
231
232 if (mOldContentProviderExists) {
233 Log.d(TAG, "Old launcher provider exists.");
234 } else {
235 Log.d(TAG, "Old launcher provider does not exist.");
236 }
237
238 mApp = app;
239 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
240 mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
241 mIconCache = iconCache;
242
243 mLauncherApps = LauncherAppsCompat.getInstance(context);
244 mUserManager = UserManagerCompat.getInstance(context);
245 }
246
247 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
248 * posted on the main thread handler. */
249 @Thunk void runOnMainThread(Runnable r) {
250 if (sWorkerThread.getThreadId() == Process.myTid()) {
251 // If we are on the worker thread, post onto the main handler
252 mHandler.post(r);
253 } else {
254 r.run();
255 }
256 }
257
258 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
259 * posted on the worker thread handler. */
260 private static void runOnWorkerThread(Runnable r) {
261 if (sWorkerThread.getThreadId() == Process.myTid()) {
262 r.run();
263 } else {
264 // If we are not on the worker thread, then post to the worker handler
265 sWorker.post(r);
266 }
267 }
268
269 /**
270 * Runs the specified runnable after the loader is complete
271 */
272 @Thunk void runAfterBindCompletes(Runnable r) {
273 if (isLoadingWorkspace() || !mHasLoaderCompletedOnce) {
274 synchronized (mBindCompleteRunnables) {
275 mBindCompleteRunnables.add(r);
276 }
277 } else {
278 runOnWorkerThread(r);
279 }
280 }
281
282 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
283 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
284 }
285
286 public void setPackageState(final PackageInstallInfo installInfo) {
287 Runnable updateRunnable = new Runnable() {
288
289 @Override
290 public void run() {
291 synchronized (sBgLock) {
292 final HashSet<ItemInfo> updates = new HashSet<>();
293
294 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
295 // Ignore install success events as they are handled by Package add events.
296 return;
297 }
298
299 for (ItemInfo info : sBgItemsIdMap) {
300 if (info instanceof ShortcutInfo) {
301 ShortcutInfo si = (ShortcutInfo) info;
302 ComponentName cn = si.getTargetComponent();
303 if (si.isPromise() && (cn != null)
304 && installInfo.packageName.equals(cn.getPackageName())) {
305 si.setInstallProgress(installInfo.progress);
306
307 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
308 // Mark this info as broken.
309 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
310 }
311 updates.add(si);
312 }
313 }
314 }
315
316 for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
317 if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
318 widget.installProgress = installInfo.progress;
319 updates.add(widget);
320 }
321 }
322
323 if (!updates.isEmpty()) {
324 // Push changes to the callback.
325 Runnable r = new Runnable() {
326 public void run() {
327 Callbacks callbacks = getCallback();
328 if (callbacks != null) {
329 callbacks.bindRestoreItemsChange(updates);
330 }
331 }
332 };
333 mHandler.post(r);
334 }
335 }
336 }
337 };
338 runOnWorkerThread(updateRunnable);
339 }
340
341 /**
342 * Updates the icons and label of all pending icons for the provided package name.
343 */
344 public void updateSessionDisplayInfo(final String packageName) {
345 Runnable updateRunnable = new Runnable() {
346
347 @Override
348 public void run() {
349 synchronized (sBgLock) {
350 final ArrayList<ShortcutInfo> updates = new ArrayList<>();
351 final UserHandleCompat user = UserHandleCompat.myUserHandle();
352
353 for (ItemInfo info : sBgItemsIdMap) {
354 if (info instanceof ShortcutInfo) {
355 ShortcutInfo si = (ShortcutInfo) info;
356 ComponentName cn = si.getTargetComponent();
357 if (si.isPromise() && (cn != null)
358 && packageName.equals(cn.getPackageName())) {
359 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
360 // For auto install apps update the icon as well as label.
361 mIconCache.getTitleAndIcon(si,
362 si.promisedIntent, user,
363 si.shouldUseLowResIcon());
364 } else {
365 // Only update the icon for restored apps.
366 si.updateIcon(mIconCache);
367 }
368 updates.add(si);
369 }
370 }
371 }
372
373 if (!updates.isEmpty()) {
374 // Push changes to the callback.
375 Runnable r = new Runnable() {
376 public void run() {
377 Callbacks callbacks = getCallback();
378 if (callbacks != null) {
379 callbacks.bindShortcutsChanged(updates,
380 new ArrayList<ShortcutInfo>(), user);
381 }
382 }
383 };
384 mHandler.post(r);
385 }
386 }
387 }
388 };
389 runOnWorkerThread(updateRunnable);
390 }
391
392 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
393 final Callbacks callbacks = getCallback();
394
395 if (allAppsApps == null) {
396 throw new RuntimeException("allAppsApps must not be null");
397 }
398 if (allAppsApps.isEmpty()) {
399 return;
400 }
401
402 // Process the newly added applications and add them to the database first
403 Runnable r = new Runnable() {
404 public void run() {
405 runOnMainThread(new Runnable() {
406 public void run() {
407 Callbacks cb = getCallback();
408 if (callbacks == cb && cb != null) {
409 callbacks.bindAppsAdded(null, null, null, allAppsApps);
410 }
411 }
412 });
413 }
414 };
415 runOnWorkerThread(r);
416 }
417
418 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos,
419 int[] xy, int spanX, int spanY) {
420 LauncherAppState app = LauncherAppState.getInstance();
421 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
422 final int xCount = (int) profile.numColumns;
423 final int yCount = (int) profile.numRows;
424 boolean[][] occupied = new boolean[xCount][yCount];
425 if (occupiedPos != null) {
426 for (ItemInfo r : occupiedPos) {
427 int right = r.cellX + r.spanX;
428 int bottom = r.cellY + r.spanY;
429 for (int x = r.cellX; 0 <= x && x < right && x < xCount; x++) {
430 for (int y = r.cellY; 0 <= y && y < bottom && y < yCount; y++) {
431 occupied[x][y] = true;
432 }
433 }
434 }
435 }
436 return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
437 }
438
439 /**
440 * Find a position on the screen for the given size or adds a new screen.
441 * @return screenId and the coordinates for the item.
442 */
443 @Thunk Pair<Long, int[]> findSpaceForItem(
444 Context context,
445 ArrayList<Long> workspaceScreens,
446 ArrayList<Long> addedWorkspaceScreensFinal,
447 int spanX, int spanY) {
448 LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
449
450 // Use sBgItemsIdMap as all the items are already loaded.
451 assertWorkspaceLoaded();
452 synchronized (sBgLock) {
453 for (ItemInfo info : sBgItemsIdMap) {
454 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
455 ArrayList<ItemInfo> items = screenItems.get(info.screenId);
456 if (items == null) {
457 items = new ArrayList<>();
458 screenItems.put(info.screenId, items);
459 }
460 items.add(info);
461 }
462 }
463 }
464
465 // Find appropriate space for the item.
466 long screenId = 0;
467 int[] cordinates = new int[2];
468 boolean found = false;
469
470 int screenCount = workspaceScreens.size();
471 // First check the preferred screen.
472 int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
473 if (preferredScreenIndex < screenCount) {
474 screenId = workspaceScreens.get(preferredScreenIndex);
475 found = findNextAvailableIconSpaceInScreen(
476 screenItems.get(screenId), cordinates, spanX, spanY);
477 }
478
479 if (!found) {
480 // Search on any of the screens starting from the first screen.
481 for (int screen = 1; screen < screenCount; screen++) {
482 screenId = workspaceScreens.get(screen);
483 if (findNextAvailableIconSpaceInScreen(
484 screenItems.get(screenId), cordinates, spanX, spanY)) {
485 // We found a space for it
486 found = true;
487 break;
488 }
489 }
490 }
491
492 if (!found) {
493 // Still no position found. Add a new screen to the end.
494 screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
495
496 // Save the screen id for binding in the workspace
497 workspaceScreens.add(screenId);
498 addedWorkspaceScreensFinal.add(screenId);
499
500 // If we still can't find an empty space, then God help us all!!!
501 if (!findNextAvailableIconSpaceInScreen(
502 screenItems.get(screenId), cordinates, spanX, spanY)) {
503 throw new RuntimeException("Can't find space to add the item");
504 }
505 }
506 return Pair.create(screenId, cordinates);
507 }
508
509 /**
510 * Adds the provided items to the workspace.
511 */
512 public void addAndBindAddedWorkspaceItems(final Context context,
513 final ArrayList<? extends ItemInfo> workspaceApps) {
514 final Callbacks callbacks = getCallback();
515 if (workspaceApps.isEmpty()) {
516 return;
517 }
518 // Process the newly added applications and add them to the database first
519 Runnable r = new Runnable() {
520 public void run() {
521 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
522 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
523
524 // Get the list of workspace screens. We need to append to this list and
525 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
526 // called.
527 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
528 synchronized(sBgLock) {
529 for (ItemInfo item : workspaceApps) {
530 if (item instanceof ShortcutInfo) {
531 // Short-circuit this logic if the icon exists somewhere on the workspace
532 if (shortcutExists(context, item.getIntent(), item.user)) {
533 continue;
534 }
535 }
536
537 // Find appropriate space for the item.
538 Pair<Long, int[]> coords = findSpaceForItem(context,
539 workspaceScreens, addedWorkspaceScreensFinal,
540 1, 1);
541 long screenId = coords.first;
542 int[] cordinates = coords.second;
543
544 ItemInfo itemInfo;
545 if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
546 itemInfo = item;
547 } else if (item instanceof AppInfo) {
548 itemInfo = ((AppInfo) item).makeShortcut();
549 } else {
550 throw new RuntimeException("Unexpected info type");
551 }
552
553 // Add the shortcut to the db
554 addItemToDatabase(context, itemInfo,
555 LauncherSettings.Favorites.CONTAINER_DESKTOP,
556 screenId, cordinates[0], cordinates[1]);
557 // Save the ShortcutInfo for binding in the workspace
558 addedShortcutsFinal.add(itemInfo);
559 }
560 }
561
562 // Update the workspace screens
563 updateWorkspaceScreenOrder(context, workspaceScreens);
564
565 if (!addedShortcutsFinal.isEmpty()) {
566 runOnMainThread(new Runnable() {
567 public void run() {
568 Callbacks cb = getCallback();
569 if (callbacks == cb && cb != null) {
570 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
571 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
572 if (!addedShortcutsFinal.isEmpty()) {
573 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
574 long lastScreenId = info.screenId;
575 for (ItemInfo i : addedShortcutsFinal) {
576 if (i.screenId == lastScreenId) {
577 addAnimated.add(i);
578 } else {
579 addNotAnimated.add(i);
580 }
581 }
582 }
583 callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
584 addNotAnimated, addAnimated, null);
585 }
586 }
587 });
588 }
589 }
590 };
591 runOnWorkerThread(r);
592 }
593
594 private void unbindItemInfosAndClearQueuedBindRunnables() {
595 if (sWorkerThread.getThreadId() == Process.myTid()) {
596 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " +
597 "main thread");
598 }
599
600 // Clear any deferred bind runnables
601 synchronized (mDeferredBindRunnables) {
602 mDeferredBindRunnables.clear();
603 }
604
605 // Remove any queued UI runnables
606 mHandler.cancelAll();
607 // Unbind all the workspace items
608 unbindWorkspaceItemsOnMainThread();
609 }
610
611 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
612 void unbindWorkspaceItemsOnMainThread() {
613 // Ensure that we don't use the same workspace items data structure on the main thread
614 // by making a copy of workspace items first.
615 final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
616 synchronized (sBgLock) {
617 tmpItems.addAll(sBgWorkspaceItems);
618 tmpItems.addAll(sBgAppWidgets);
619 }
620 Runnable r = new Runnable() {
621 @Override
622 public void run() {
623 for (ItemInfo item : tmpItems) {
624 item.unbind();
625 }
626 }
627 };
628 runOnMainThread(r);
629 }
630
631 /**
632 * Adds an item to the DB if it was not created previously, or move it to a new
633 * <container, screen, cellX, cellY>
634 */
635 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container,
636 long screenId, int cellX, int cellY) {
637 if (item.container == ItemInfo.NO_ID) {
638 // From all apps
639 addItemToDatabase(context, item, container, screenId, cellX, cellY);
640 } else {
641 // From somewhere else
642 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
643 }
644 }
645
646 static void checkItemInfoLocked(
647 final long itemId, final ItemInfo item, StackTraceElement[] stackTrace) {
648 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
649 if (modelItem != null && item != modelItem) {
650 // check all the data is consistent
651 if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
652 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
653 ShortcutInfo shortcut = (ShortcutInfo) item;
654 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
655 modelShortcut.intent.filterEquals(shortcut.intent) &&
656 modelShortcut.id == shortcut.id &&
657 modelShortcut.itemType == shortcut.itemType &&
658 modelShortcut.container == shortcut.container &&
659 modelShortcut.screenId == shortcut.screenId &&
660 modelShortcut.cellX == shortcut.cellX &&
661 modelShortcut.cellY == shortcut.cellY &&
662 modelShortcut.spanX == shortcut.spanX &&
663 modelShortcut.spanY == shortcut.spanY &&
664 ((modelShortcut.dropPos == null && shortcut.dropPos == null) ||
665 (modelShortcut.dropPos != null &&
666 shortcut.dropPos != null &&
667 modelShortcut.dropPos[0] == shortcut.dropPos[0] &&
668 modelShortcut.dropPos[1] == shortcut.dropPos[1]))) {
669 // For all intents and purposes, this is the same object
670 return;
671 }
672 }
673
674 // the modelItem needs to match up perfectly with item if our model is
675 // to be consistent with the database-- for now, just require
676 // modelItem == item or the equality check above
677 String msg = "item: " + ((item != null) ? item.toString() : "null") +
678 "modelItem: " +
679 ((modelItem != null) ? modelItem.toString() : "null") +
680 "Error: ItemInfo passed to checkItemInfo doesn't match original";
681 RuntimeException e = new RuntimeException(msg);
682 if (stackTrace != null) {
683 e.setStackTrace(stackTrace);
684 }
685 throw e;
686 }
687 }
688
689 static void checkItemInfo(final ItemInfo item) {
690 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
691 final long itemId = item.id;
692 Runnable r = new Runnable() {
693 public void run() {
694 synchronized (sBgLock) {
695 checkItemInfoLocked(itemId, item, stackTrace);
696 }
697 }
698 };
699 runOnWorkerThread(r);
700 }
701
702 static void updateItemInDatabaseHelper(Context context, final ContentValues values,
703 final ItemInfo item, final String callingFunction) {
704 final long itemId = item.id;
705 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
706 final ContentResolver cr = context.getContentResolver();
707
708 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
709 Runnable r = new Runnable() {
710 public void run() {
711 cr.update(uri, values, null, null);
712 updateItemArrays(item, itemId, stackTrace);
713 }
714 };
715 runOnWorkerThread(r);
716 }
717
718 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList,
719 final ArrayList<ItemInfo> items, final String callingFunction) {
720 final ContentResolver cr = context.getContentResolver();
721
722 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
723 Runnable r = new Runnable() {
724 public void run() {
725 ArrayList<ContentProviderOperation> ops =
726 new ArrayList<ContentProviderOperation>();
727 int count = items.size();
728 for (int i = 0; i < count; i++) {
729 ItemInfo item = items.get(i);
730 final long itemId = item.id;
731 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
732 ContentValues values = valuesList.get(i);
733
734 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
735 updateItemArrays(item, itemId, stackTrace);
736
737 }
738 try {
739 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
740 } catch (Exception e) {
741 e.printStackTrace();
742 }
743 }
744 };
745 runOnWorkerThread(r);
746 }
747
748 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
749 // Lock on mBgLock *after* the db operation
750 synchronized (sBgLock) {
751 checkItemInfoLocked(itemId, item, stackTrace);
752
753 if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
754 item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
755 // Item is in a folder, make sure this folder exists
756 if (!sBgFolders.containsKey(item.container)) {
757 // An items container is being set to a that of an item which is not in
758 // the list of Folders.
759 String msg = "item: " + item + " container being set to: " +
760 item.container + ", not in the list of folders";
761 Log.e(TAG, msg);
762 }
763 }
764
765 // Items are added/removed from the corresponding FolderInfo elsewhere, such
766 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
767 // that are on the desktop, as appropriate
768 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
769 if (modelItem != null &&
770 (modelItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
771 modelItem.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)) {
772 switch (modelItem.itemType) {
773 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
774 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
775 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
776 if (!sBgWorkspaceItems.contains(modelItem)) {
777 sBgWorkspaceItems.add(modelItem);
778 }
779 break;
780 default:
781 break;
782 }
783 } else {
784 sBgWorkspaceItems.remove(modelItem);
785 }
786 }
787 }
788
789 /**
790 * Move an item in the DB to a new <container, screen, cellX, cellY>
791 */
792 public static void moveItemInDatabase(Context context, final ItemInfo item, final long container,
793 final long screenId, final int cellX, final int cellY) {
794 item.container = container;
795 item.cellX = cellX;
796 item.cellY = cellY;
797
798 // We store hotseat items in canonical form which is this orientation invariant position
799 // in the hotseat
800 if (context instanceof Launcher && screenId < 0 &&
801 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
802 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
803 } else {
804 item.screenId = screenId;
805 }
806
807 final ContentValues values = new ContentValues();
808 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
809 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
810 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
811 values.put(LauncherSettings.Favorites.RANK, item.rank);
812 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
813
814 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
815 }
816
817 /**
818 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
819 * cellX, cellY have already been updated on the ItemInfos.
820 */
821 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
822 final long container, final int screen) {
823
824 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
825 int count = items.size();
826
827 for (int i = 0; i < count; i++) {
828 ItemInfo item = items.get(i);
829 item.container = container;
830
831 // We store hotseat items in canonical form which is this orientation invariant position
832 // in the hotseat
833 if (context instanceof Launcher && screen < 0 &&
834 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
835 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
836 item.cellY);
837 } else {
838 item.screenId = screen;
839 }
840
841 final ContentValues values = new ContentValues();
842 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
843 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
844 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
845 values.put(LauncherSettings.Favorites.RANK, item.rank);
846 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
847
848 contentValues.add(values);
849 }
850 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
851 }
852
853 /**
854 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
855 */
856 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
857 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
858 item.container = container;
859 item.cellX = cellX;
860 item.cellY = cellY;
861 item.spanX = spanX;
862 item.spanY = spanY;
863
864 // We store hotseat items in canonical form which is this orientation invariant position
865 // in the hotseat
866 if (context instanceof Launcher && screenId < 0 &&
867 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
868 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
869 } else {
870 item.screenId = screenId;
871 }
872
873 final ContentValues values = new ContentValues();
874 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
875 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
876 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
877 values.put(LauncherSettings.Favorites.RANK, item.rank);
878 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
879 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
880 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
881
882 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
883 }
884
885 /**
886 * Update an item to the database in a specified container.
887 */
888 public static void updateItemInDatabase(Context context, final ItemInfo item) {
889 final ContentValues values = new ContentValues();
890 item.onAddToDatabase(context, values);
891 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
892 }
893
894 private void assertWorkspaceLoaded() {
895 if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
896 throw new RuntimeException("Trying to add shortcut while loader is running");
897 }
898 }
899
900 /**
901 * Returns true if the shortcuts already exists on the workspace. This must be called after
902 * the workspace has been loaded. We identify a shortcut by its intent.
903 */
904 @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
905 assertWorkspaceLoaded();
906 final String intentWithPkg, intentWithoutPkg;
907 if (intent.getComponent() != null) {
908 // If component is not null, an intent with null package will produce
909 // the same result and should also be a match.
910 String packageName = intent.getComponent().getPackageName();
911 if (intent.getPackage() != null) {
912 intentWithPkg = intent.toUri(0);
913 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
914 } else {
915 intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
916 intentWithoutPkg = intent.toUri(0);
917 }
918 } else {
919 intentWithPkg = intent.toUri(0);
920 intentWithoutPkg = intent.toUri(0);
921 }
922
923 synchronized (sBgLock) {
924 for (ItemInfo item : sBgItemsIdMap) {
925 if (item instanceof ShortcutInfo) {
926 ShortcutInfo info = (ShortcutInfo) item;
927 Intent targetIntent = info.promisedIntent == null
928 ? info.intent : info.promisedIntent;
929 if (targetIntent != null && info.user.equals(user)) {
930 String s = targetIntent.toUri(0);
931 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
932 return true;
933 }
934 }
935 }
936 }
937 }
938 return false;
939 }
940
941 /**
942 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
943 */
944 FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) {
945 final ContentResolver cr = context.getContentResolver();
946 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null,
947 "_id=? and (itemType=? or itemType=?)",
948 new String[] { String.valueOf(id),
949 String.valueOf(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)}, null);
950
951 try {
952 if (c.moveToFirst()) {
953 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
954 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
955 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
956 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
957 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
958 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
959 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
960
961 FolderInfo folderInfo = null;
962 switch (c.getInt(itemTypeIndex)) {
963 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
964 folderInfo = findOrMakeFolder(folderList, id);
965 break;
966 }
967
968 // Do not trim the folder label, as is was set by the user.
969 folderInfo.title = c.getString(titleIndex);
970 folderInfo.id = id;
971 folderInfo.container = c.getInt(containerIndex);
972 folderInfo.screenId = c.getInt(screenIndex);
973 folderInfo.cellX = c.getInt(cellXIndex);
974 folderInfo.cellY = c.getInt(cellYIndex);
975 folderInfo.options = c.getInt(optionsIndex);
976
977 return folderInfo;
978 }
979 } finally {
980 c.close();
981 }
982
983 return null;
984 }
985
986 /**
987 * Add an item to the database in a specified container. Sets the container, screen, cellX and
988 * cellY fields of the item. Also assigns an ID to the item.
989 */
990 public static void addItemToDatabase(Context context, final ItemInfo item, final long container,
991 final long screenId, final int cellX, final int cellY) {
992 item.container = container;
993 item.cellX = cellX;
994 item.cellY = cellY;
995 // We store hotseat items in canonical form which is this orientation invariant position
996 // in the hotseat
997 if (context instanceof Launcher && screenId < 0 &&
998 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
999 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
1000 } else {
1001 item.screenId = screenId;
1002 }
1003
1004 final ContentValues values = new ContentValues();
1005 final ContentResolver cr = context.getContentResolver();
1006 item.onAddToDatabase(context, values);
1007
1008 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
1009 values.put(LauncherSettings.Favorites._ID, item.id);
1010
1011 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
1012 Runnable r = new Runnable() {
1013 public void run() {
1014 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
1015
1016 // Lock on mBgLock *after* the db operation
1017 synchronized (sBgLock) {
1018 checkItemInfoLocked(item.id, item, stackTrace);
1019 sBgItemsIdMap.put(item.id, item);
1020 switch (item.itemType) {
1021 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1022 sBgFolders.put(item.id, (FolderInfo) item);
1023 // Fall through
1024 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1025 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1026 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
1027 item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1028 sBgWorkspaceItems.add(item);
1029 } else {
1030 if (!sBgFolders.containsKey(item.container)) {
1031 // Adding an item to a folder that doesn't exist.
1032 String msg = "adding item: " + item + " to a folder that " +
1033 " doesn't exist";
1034 Log.e(TAG, msg);
1035 }
1036 }
1037 break;
1038 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1039 sBgAppWidgets.add((LauncherAppWidgetInfo) item);
1040 break;
1041 }
1042 }
1043 }
1044 };
1045 runOnWorkerThread(r);
1046 }
1047
1048 /**
1049 * Creates a new unique child id, for a given cell span across all layouts.
1050 */
1051 static int getCellLayoutChildId(
1052 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
1053 return (((int) container & 0xFF) << 24)
1054 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1055 }
1056
1057 private static ArrayList<ItemInfo> getItemsByPackageName(
1058 final String pn, final UserHandleCompat user) {
1059 ItemInfoFilter filter = new ItemInfoFilter() {
1060 @Override
1061 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
1062 return cn.getPackageName().equals(pn) && info.user.equals(user);
1063 }
1064 };
1065 return filterItemInfos(sBgItemsIdMap, filter);
1066 }
1067
1068 /**
1069 * Removes all the items from the database corresponding to the specified package.
1070 */
1071 static void deletePackageFromDatabase(Context context, final String pn,
1072 final UserHandleCompat user) {
1073 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
1074 }
1075
1076 /**
1077 * Removes the specified item from the database
1078 * @param context
1079 * @param item
1080 */
1081 public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1082 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1083 items.add(item);
1084 deleteItemsFromDatabase(context, items);
1085 }
1086
1087 /**
1088 * Removes the specified items from the database
1089 * @param context
1090 * @param item
1091 */
1092 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
1093 final ContentResolver cr = context.getContentResolver();
1094 Runnable r = new Runnable() {
1095 public void run() {
1096 for (ItemInfo item : items) {
1097 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
1098 cr.delete(uri, null, null);
1099
1100 // Lock on mBgLock *after* the db operation
1101 synchronized (sBgLock) {
1102 switch (item.itemType) {
1103 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
1104 sBgFolders.remove(item.id);
1105 for (ItemInfo info: sBgItemsIdMap) {
1106 if (info.container == item.id) {
1107 // We are deleting a folder which still contains items that
1108 // think they are contained by that folder.
1109 String msg = "deleting a folder (" + item + ") which still " +
1110 "contains items (" + info + ")";
1111 Log.e(TAG, msg);
1112 }
1113 }
1114 sBgWorkspaceItems.remove(item);
1115 break;
1116 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1117 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1118 sBgWorkspaceItems.remove(item);
1119 break;
1120 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1121 sBgAppWidgets.remove((LauncherAppWidgetInfo) item);
1122 break;
1123 }
1124 sBgItemsIdMap.remove(item.id);
1125 }
1126 }
1127 }
1128 };
1129 runOnWorkerThread(r);
1130 }
1131
1132 /**
1133 * Update the order of the workspace screens in the database. The array list contains
1134 * a list of screen ids in the order that they should appear.
1135 */
1136 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1137 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1138 final ContentResolver cr = context.getContentResolver();
1139 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1140
1141 // Remove any negative screen ids -- these aren't persisted
1142 Iterator<Long> iter = screensCopy.iterator();
1143 while (iter.hasNext()) {
1144 long id = iter.next();
1145 if (id < 0) {
1146 iter.remove();
1147 }
1148 }
1149
1150 Runnable r = new Runnable() {
1151 @Override
1152 public void run() {
1153 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1154 // Clear the table
1155 ops.add(ContentProviderOperation.newDelete(uri).build());
1156 int count = screensCopy.size();
1157 for (int i = 0; i < count; i++) {
1158 ContentValues v = new ContentValues();
1159 long screenId = screensCopy.get(i);
1160 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1161 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1162 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1163 }
1164
1165 try {
1166 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1167 } catch (Exception ex) {
1168 throw new RuntimeException(ex);
1169 }
1170
1171 synchronized (sBgLock) {
1172 sBgWorkspaceScreens.clear();
1173 sBgWorkspaceScreens.addAll(screensCopy);
1174 }
1175 }
1176 };
1177 runOnWorkerThread(r);
1178 }
1179
1180 /**
1181 * Remove the contents of the specified folder from the database
1182 */
1183 public static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1184 final ContentResolver cr = context.getContentResolver();
1185
1186 Runnable r = new Runnable() {
1187 public void run() {
1188 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
1189 // Lock on mBgLock *after* the db operation
1190 synchronized (sBgLock) {
1191 sBgItemsIdMap.remove(info.id);
1192 sBgFolders.remove(info.id);
1193 sBgWorkspaceItems.remove(info);
1194 }
1195
1196 cr.delete(LauncherSettings.Favorites.CONTENT_URI,
1197 LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
1198 // Lock on mBgLock *after* the db operation
1199 synchronized (sBgLock) {
1200 for (ItemInfo childInfo : info.contents) {
1201 sBgItemsIdMap.remove(childInfo.id);
1202 }
1203 }
1204 }
1205 };
1206 runOnWorkerThread(r);
1207 }
1208
1209 /**
1210 * Set this as the current Launcher activity object for the loader.
1211 */
1212 public void initialize(Callbacks callbacks) {
1213 synchronized (mLock) {
1214 // Disconnect any of the callbacks and drawables associated with ItemInfos on the
1215 // workspace to prevent leaking Launcher activities on orientation change.
1216 unbindItemInfosAndClearQueuedBindRunnables();
1217 mCallbacks = new WeakReference<Callbacks>(callbacks);
1218 }
1219 }
1220
1221 @Override
1222 public void onPackageChanged(String packageName, UserHandleCompat user) {
1223 int op = PackageUpdatedTask.OP_UPDATE;
1224 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1225 user));
1226 }
1227
1228 @Override
1229 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1230 int op = PackageUpdatedTask.OP_REMOVE;
1231 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1232 user));
1233 }
1234
1235 @Override
1236 public void onPackageAdded(String packageName, UserHandleCompat user) {
1237 int op = PackageUpdatedTask.OP_ADD;
1238 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1239 user));
1240 }
1241
1242 @Override
1243 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1244 boolean replacing) {
1245 if (!replacing) {
1246 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
1247 user));
1248 if (mAppsCanBeOnRemoveableStorage) {
1249 // Only rebind if we support removable storage. It catches the
1250 // case where
1251 // apps on the external sd card need to be reloaded
1252 startLoaderFromBackground();
1253 }
1254 } else {
1255 // If we are replacing then just update the packages in the list
1256 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1257 packageNames, user));
1258 }
1259 }
1260
1261 @Override
1262 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1263 boolean replacing) {
1264 if (!replacing) {
1265 enqueuePackageUpdated(new PackageUpdatedTask(
1266 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1267 user));
1268 }
1269 }
1270
1271 /**
1272 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1273 * ACTION_PACKAGE_CHANGED.
1274 */
1275 @Override
1276 public void onReceive(Context context, Intent intent) {
1277 if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
1278
1279 final String action = intent.getAction();
1280 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1281 // If we have changed locale we need to clear out the labels in all apps/workspace.
1282 forceReload();
1283 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) ||
1284 SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action)) {
1285 Callbacks callbacks = getCallback();
1286 if (callbacks != null) {
1287 callbacks.bindSearchablesChanged();
1288 }
1289 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
1290 || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) {
1291 forceReload();
1292 }
1293 }
1294
1295 void forceReload() {
1296 resetLoadedState(true, true);
1297
1298 // Do this here because if the launcher activity is running it will be restarted.
1299 // If it's not running startLoaderFromBackground will merely tell it that it needs
1300 // to reload.
1301 startLoaderFromBackground();
1302 }
1303
1304 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1305 synchronized (mLock) {
1306 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1307 // mWorkspaceLoaded to true later
1308 stopLoaderLocked();
1309 if (resetAllAppsLoaded) mAllAppsLoaded = false;
1310 if (resetWorkspaceLoaded) mWorkspaceLoaded = false;
1311 }
1312 }
1313
1314 /**
1315 * When the launcher is in the background, it's possible for it to miss paired
1316 * configuration changes. So whenever we trigger the loader from the background
1317 * tell the launcher that it needs to re-run the loader when it comes back instead
1318 * of doing it now.
1319 */
1320 public void startLoaderFromBackground() {
1321 boolean runLoader = false;
1322 Callbacks callbacks = getCallback();
1323 if (callbacks != null) {
1324 // Only actually run the loader if they're not paused.
1325 if (!callbacks.setLoadOnResume()) {
1326 runLoader = true;
1327 }
1328 }
1329 if (runLoader) {
1330 startLoader(PagedView.INVALID_RESTORE_PAGE);
1331 }
1332 }
1333
1334 /**
1335 * If there is already a loader task running, tell it to stop.
1336 */
1337 private void stopLoaderLocked() {
1338 LoaderTask oldTask = mLoaderTask;
1339 if (oldTask != null) {
1340 oldTask.stopLocked();
1341 }
1342 }
1343
1344 public boolean isCurrentCallbacks(Callbacks callbacks) {
1345 return (mCallbacks != null && mCallbacks.get() == callbacks);
1346 }
1347
1348 public void startLoader(int synchronousBindPage) {
1349 startLoader(synchronousBindPage, LOADER_FLAG_NONE);
1350 }
1351
1352 public void startLoader(int synchronousBindPage, int loadFlags) {
1353 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
1354 InstallShortcutReceiver.enableInstallQueue();
1355 synchronized (mLock) {
1356 // Clear any deferred bind-runnables from the synchronized load process
1357 // We must do this before any loading/binding is scheduled below.
1358 synchronized (mDeferredBindRunnables) {
1359 mDeferredBindRunnables.clear();
1360 }
1361
1362 // Don't bother to start the thread if we know it's not going to do anything
1363 if (mCallbacks != null && mCallbacks.get() != null) {
1364 // If there is already one running, tell it to stop.
1365 stopLoaderLocked();
1366 mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
1367 if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
1368 && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) {
1369 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1370 } else {
1371 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1372 sWorker.post(mLoaderTask);
1373 }
1374 }
1375 }
1376 }
1377
1378 void bindRemainingSynchronousPages() {
1379 // Post the remaining side pages to be loaded
1380 if (!mDeferredBindRunnables.isEmpty()) {
1381 Runnable[] deferredBindRunnables = null;
1382 synchronized (mDeferredBindRunnables) {
1383 deferredBindRunnables = mDeferredBindRunnables.toArray(
1384 new Runnable[mDeferredBindRunnables.size()]);
1385 mDeferredBindRunnables.clear();
1386 }
1387 for (final Runnable r : deferredBindRunnables) {
1388 mHandler.post(r);
1389 }
1390 }
1391
1392 // Run all the bind complete runnables after workspace is bound.
1393 if (!mBindCompleteRunnables.isEmpty()) {
1394 synchronized (mBindCompleteRunnables) {
1395 for (final Runnable r : mBindCompleteRunnables) {
1396 runOnWorkerThread(r);
1397 }
1398 mBindCompleteRunnables.clear();
1399 }
1400 }
1401 }
1402
1403 public void stopLoader() {
1404 synchronized (mLock) {
1405 if (mLoaderTask != null) {
1406 mLoaderTask.stopLocked();
1407 }
1408 }
1409 }
1410
1411 /**
1412 * Loads the workspace screen ids in an ordered list.
1413 */
1414 @Thunk static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1415 final ContentResolver contentResolver = context.getContentResolver();
1416 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1417
1418 // Get screens ordered by rank.
1419 final Cursor sc = contentResolver.query(screensUri, null, null, null,
1420 LauncherSettings.WorkspaceScreens.SCREEN_RANK);
1421 ArrayList<Long> screenIds = new ArrayList<Long>();
1422 try {
1423 final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
1424 while (sc.moveToNext()) {
1425 try {
1426 screenIds.add(sc.getLong(idIndex));
1427 } catch (Exception e) {
1428 Launcher.addDumpLog(TAG, "Desktop items loading interrupted"
1429 + " - invalid screens: " + e, true);
1430 }
1431 }
1432 } finally {
1433 sc.close();
1434 }
1435 return screenIds;
1436 }
1437
1438 public boolean isAllAppsLoaded() {
1439 return mAllAppsLoaded;
1440 }
1441
1442 boolean isLoadingWorkspace() {
1443 synchronized (mLock) {
1444 if (mLoaderTask != null) {
1445 return mLoaderTask.isLoadingWorkspace();
1446 }
1447 }
1448 return false;
1449 }
1450
1451 /**
1452 * Runnable for the thread that loads the contents of the launcher:
1453 * - workspace icons
1454 * - widgets
1455 * - all apps icons
1456 */
1457 private class LoaderTask implements Runnable {
1458 private Context mContext;
1459 @Thunk boolean mIsLoadingAndBindingWorkspace;
1460 private boolean mStopped;
1461 @Thunk boolean mLoadAndBindStepFinished;
1462 private int mFlags;
1463
1464 LoaderTask(Context context, int flags) {
1465 mContext = context;
1466 mFlags = flags;
1467 }
1468
1469 boolean isLoadingWorkspace() {
1470 return mIsLoadingAndBindingWorkspace;
1471 }
1472
1473 private void loadAndBindWorkspace() {
1474 mIsLoadingAndBindingWorkspace = true;
1475
1476 // Load the workspace
1477 if (DEBUG_LOADERS) {
1478 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1479 }
1480
1481 if (!mWorkspaceLoaded) {
1482 loadWorkspace();
1483 synchronized (LoaderTask.this) {
1484 if (mStopped) {
1485 return;
1486 }
1487 mWorkspaceLoaded = true;
1488 }
1489 }
1490
1491 // Bind the workspace
1492 bindWorkspace(-1);
1493 }
1494
1495 private void waitForIdle() {
1496 // Wait until the either we're stopped or the other threads are done.
1497 // This way we don't start loading all apps until the workspace has settled
1498 // down.
1499 synchronized (LoaderTask.this) {
1500 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1501
1502 mHandler.postIdle(new Runnable() {
1503 public void run() {
1504 synchronized (LoaderTask.this) {
1505 mLoadAndBindStepFinished = true;
1506 if (DEBUG_LOADERS) {
1507 Log.d(TAG, "done with previous binding step");
1508 }
1509 LoaderTask.this.notify();
1510 }
1511 }
1512 });
1513
1514 while (!mStopped && !mLoadAndBindStepFinished) {
1515 try {
1516 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1517 // wait no longer than 1sec at a time
1518 this.wait(1000);
1519 } catch (InterruptedException ex) {
1520 // Ignore
1521 }
1522 }
1523 if (DEBUG_LOADERS) {
1524 Log.d(TAG, "waited "
1525 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1526 + "ms for previous step to finish binding");
1527 }
1528 }
1529 }
1530
1531 void runBindSynchronousPage(int synchronousBindPage) {
1532 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1533 // Ensure that we have a valid page index to load synchronously
1534 throw new RuntimeException("Should not call runBindSynchronousPage() without " +
1535 "valid page index");
1536 }
1537 if (!mAllAppsLoaded || !mWorkspaceLoaded) {
1538 // Ensure that we don't try and bind a specified page when the pages have not been
1539 // loaded already (we should load everything asynchronously in that case)
1540 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1541 }
1542 synchronized (mLock) {
1543 if (mIsLoaderTaskRunning) {
1544 // Ensure that we are never running the background loading at this point since
1545 // we also touch the background collections
1546 throw new RuntimeException("Error! Background loading is already running");
1547 }
1548 }
1549
1550 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1551 // data structures, we can't allow any other thread to touch that data, but because
1552 // this call is synchronous, we can get away with not locking).
1553
1554 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1555 // operations from the previous activity. We need to ensure that all queued operations
1556 // are executed before any synchronous binding work is done.
1557 mHandler.flush();
1558
1559 // Divide the set of loaded items into those that we are binding synchronously, and
1560 // everything else that is to be bound normally (asynchronously).
1561 bindWorkspace(synchronousBindPage);
1562 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1563 // arise from that.
1564 onlyBindAllApps();
1565 }
1566
1567 public void run() {
1568 synchronized (mLock) {
1569 if (mStopped) {
1570 return;
1571 }
1572 mIsLoaderTaskRunning = true;
1573 }
1574 // Optimize for end-user experience: if the Launcher is up and // running with the
1575 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1576 // workspace first (default).
1577 keep_running: {
1578 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
1579 loadAndBindWorkspace();
1580
1581 if (mStopped) {
1582 break keep_running;
1583 }
1584
1585 waitForIdle();
1586
1587 // second step
1588 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
1589 loadAndBindAllApps();
1590 }
1591
1592 // Clear out this reference, otherwise we end up holding it until all of the
1593 // callback runnables are done.
1594 mContext = null;
1595
1596 synchronized (mLock) {
1597 // If we are still the last one to be scheduled, remove ourselves.
1598 if (mLoaderTask == this) {
1599 mLoaderTask = null;
1600 }
1601 mIsLoaderTaskRunning = false;
1602 mHasLoaderCompletedOnce = true;
1603 }
1604 }
1605
1606 public void stopLocked() {
1607 synchronized (LoaderTask.this) {
1608 mStopped = true;
1609 this.notify();
1610 }
1611 }
1612
1613 /**
1614 * Gets the callbacks object. If we've been stopped, or if the launcher object
1615 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1616 * object that was around when the deferred message was scheduled, and if there's
1617 * a new Callbacks object around then also return null. This will save us from
1618 * calling onto it with data that will be ignored.
1619 */
1620 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1621 synchronized (mLock) {
1622 if (mStopped) {
1623 return null;
1624 }
1625
1626 if (mCallbacks == null) {
1627 return null;
1628 }
1629
1630 final Callbacks callbacks = mCallbacks.get();
1631 if (callbacks != oldCallbacks) {
1632 return null;
1633 }
1634 if (callbacks == null) {
1635 Log.w(TAG, "no mCallbacks");
1636 return null;
1637 }
1638
1639 return callbacks;
1640 }
1641 }
1642
1643 // check & update map of what's occupied; used to discard overlapping/invalid items
1644 private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) {
1645 LauncherAppState app = LauncherAppState.getInstance();
1646 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1647 final int countX = (int) profile.numColumns;
1648 final int countY = (int) profile.numRows;
1649
1650 long containerIndex = item.screenId;
1651 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1652 // Return early if we detect that an item is under the hotseat button
1653 if (mCallbacks == null ||
1654 mCallbacks.get().isAllAppsButtonRank((int) item.screenId)) {
1655 Log.e(TAG, "Error loading shortcut into hotseat " + item
1656 + " into position (" + item.screenId + ":" + item.cellX + ","
1657 + item.cellY + ") occupied by all apps");
1658 return false;
1659 }
1660
1661 final ItemInfo[][] hotseatItems =
1662 occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT);
1663
1664 if (item.screenId >= profile.numHotseatIcons) {
1665 Log.e(TAG, "Error loading shortcut " + item
1666 + " into hotseat position " + item.screenId
1667 + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1)
1668 + ")");
1669 return false;
1670 }
1671
1672 if (hotseatItems != null) {
1673 if (hotseatItems[(int) item.screenId][0] != null) {
1674 Log.e(TAG, "Error loading shortcut into hotseat " + item
1675 + " into position (" + item.screenId + ":" + item.cellX + ","
1676 + item.cellY + ") occupied by "
1677 + occupied.get(LauncherSettings.Favorites.CONTAINER_HOTSEAT)
1678 [(int) item.screenId][0]);
1679 return false;
1680 } else {
1681 hotseatItems[(int) item.screenId][0] = item;
1682 return true;
1683 }
1684 } else {
1685 final ItemInfo[][] items = new ItemInfo[(int) profile.numHotseatIcons][1];
1686 items[(int) item.screenId][0] = item;
1687 occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, items);
1688 return true;
1689 }
1690 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1691 // Skip further checking if it is not the hotseat or workspace container
1692 return true;
1693 }
1694
1695 if (!occupied.containsKey(item.screenId)) {
1696 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1697 occupied.put(item.screenId, items);
1698 }
1699
1700 final ItemInfo[][] screens = occupied.get(item.screenId);
1701 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
1702 item.cellX < 0 || item.cellY < 0 ||
1703 item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) {
1704 Log.e(TAG, "Error loading shortcut " + item
1705 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1706 + item.cellX + "," + item.cellY
1707 + ") out of screen bounds ( " + countX + "x" + countY + ")");
1708 return false;
1709 }
1710
1711 // Check if any workspace icons overlap with each other
1712 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1713 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1714 if (screens[x][y] != null) {
1715 Log.e(TAG, "Error loading shortcut " + item
1716 + " into cell (" + containerIndex + "-" + item.screenId + ":"
1717 + x + "," + y
1718 + ") occupied by "
1719 + screens[x][y]);
1720 return false;
1721 }
1722 }
1723 }
1724 for (int x = item.cellX; x < (item.cellX+item.spanX); x++) {
1725 for (int y = item.cellY; y < (item.cellY+item.spanY); y++) {
1726 screens[x][y] = item;
1727 }
1728 }
1729
1730 return true;
1731 }
1732
1733 /** Clears all the sBg data structures */
1734 private void clearSBgDataStructures() {
1735 synchronized (sBgLock) {
1736 sBgWorkspaceItems.clear();
1737 sBgAppWidgets.clear();
1738 sBgFolders.clear();
1739 sBgItemsIdMap.clear();
1740 sBgWorkspaceScreens.clear();
1741 }
1742 }
1743
1744 private void loadWorkspace() {
1745 final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1746
1747 final Context context = mContext;
1748 final ContentResolver contentResolver = context.getContentResolver();
1749 final PackageManager manager = context.getPackageManager();
1750 final boolean isSafeMode = manager.isSafeMode();
1751 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1752 final boolean isSdCardReady = context.registerReceiver(null,
1753 new IntentFilter(StartupReceiver.SYSTEM_READY)) != null;
1754
1755 LauncherAppState app = LauncherAppState.getInstance();
1756 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1757 int countX = (int) profile.numColumns;
1758 int countY = (int) profile.numRows;
1759
1760 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1761 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1762 LauncherAppState.getLauncherProvider().deleteDatabase();
1763 }
1764
1765 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1766 // append the user's Launcher2 shortcuts
1767 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1768 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1769 } else {
1770 // Make sure the default workspace is loaded
1771 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1772 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
1773 }
1774
1775 synchronized (sBgLock) {
1776 clearSBgDataStructures();
1777 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
1778 .getInstance(mContext).updateAndGetActiveSessionCache();
1779
1780 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1781 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1782 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1783 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
1784 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1785
1786 // +1 for the hotseat (it can be larger than the workspace)
1787 // Load workspace in reverse order to ensure that latest items are loaded first (and
1788 // before any earlier duplicates)
1789 final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
1790
1791 try {
1792 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1793 final int intentIndex = c.getColumnIndexOrThrow
1794 (LauncherSettings.Favorites.INTENT);
1795 final int titleIndex = c.getColumnIndexOrThrow
1796 (LauncherSettings.Favorites.TITLE);
1797 final int containerIndex = c.getColumnIndexOrThrow(
1798 LauncherSettings.Favorites.CONTAINER);
1799 final int itemTypeIndex = c.getColumnIndexOrThrow(
1800 LauncherSettings.Favorites.ITEM_TYPE);
1801 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
1802 LauncherSettings.Favorites.APPWIDGET_ID);
1803 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
1804 LauncherSettings.Favorites.APPWIDGET_PROVIDER);
1805 final int screenIndex = c.getColumnIndexOrThrow(
1806 LauncherSettings.Favorites.SCREEN);
1807 final int cellXIndex = c.getColumnIndexOrThrow
1808 (LauncherSettings.Favorites.CELLX);
1809 final int cellYIndex = c.getColumnIndexOrThrow
1810 (LauncherSettings.Favorites.CELLY);
1811 final int spanXIndex = c.getColumnIndexOrThrow
1812 (LauncherSettings.Favorites.SPANX);
1813 final int spanYIndex = c.getColumnIndexOrThrow(
1814 LauncherSettings.Favorites.SPANY);
1815 final int rankIndex = c.getColumnIndexOrThrow(
1816 LauncherSettings.Favorites.RANK);
1817 final int restoredIndex = c.getColumnIndexOrThrow(
1818 LauncherSettings.Favorites.RESTORED);
1819 final int profileIdIndex = c.getColumnIndexOrThrow(
1820 LauncherSettings.Favorites.PROFILE_ID);
1821 final int optionsIndex = c.getColumnIndexOrThrow(
1822 LauncherSettings.Favorites.OPTIONS);
1823 final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
1824
1825 final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
1826 for (UserHandleCompat user : mUserManager.getUserProfiles()) {
1827 allUsers.put(mUserManager.getSerialNumberForUser(user), user);
1828 }
1829
1830 ShortcutInfo info;
1831 String intentDescription;
1832 LauncherAppWidgetInfo appWidgetInfo;
1833 int container;
1834 long id;
1835 long serialNumber;
1836 Intent intent;
1837 UserHandleCompat user;
1838
1839 while (!mStopped && c.moveToNext()) {
1840 try {
1841 int itemType = c.getInt(itemTypeIndex);
1842 boolean restored = 0 != c.getInt(restoredIndex);
1843 boolean allowMissingTarget = false;
1844 container = c.getInt(containerIndex);
1845
1846 switch (itemType) {
1847 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
1848 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1849 id = c.getLong(idIndex);
1850 intentDescription = c.getString(intentIndex);
1851 serialNumber = c.getInt(profileIdIndex);
1852 user = allUsers.get(serialNumber);
1853 int promiseType = c.getInt(restoredIndex);
1854 int disabledState = 0;
1855 boolean itemReplaced = false;
1856 if (user == null) {
1857 // User has been deleted remove the item.
1858 itemsToRemove.add(id);
1859 continue;
1860 }
1861 try {
1862 intent = Intent.parseUri(intentDescription, 0);
1863 ComponentName cn = intent.getComponent();
1864 if (cn != null && cn.getPackageName() != null) {
1865 boolean validPkg = launcherApps.isPackageEnabledForProfile(
1866 cn.getPackageName(), user);
1867 boolean validComponent = validPkg &&
1868 launcherApps.isActivityEnabledForProfile(cn, user);
1869
1870 if (validComponent) {
1871 if (restored) {
1872 // no special handling necessary for this item
1873 restoredRows.add(id);
1874 restored = false;
1875 }
1876 } else if (validPkg) {
1877 intent = null;
1878 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
1879 // We allow auto install apps to have their intent
1880 // updated after an install.
1881 intent = manager.getLaunchIntentForPackage(
1882 cn.getPackageName());
1883 if (intent != null) {
1884 ContentValues values = new ContentValues();
1885 values.put(LauncherSettings.Favorites.INTENT,
1886 intent.toUri(0));
1887 updateItem(id, values);
1888 }
1889 }
1890
1891 if (intent == null) {
1892 // The app is installed but the component is no
1893 // longer available.
1894 Launcher.addDumpLog(TAG,
1895 "Invalid component removed: " + cn, true);
1896 itemsToRemove.add(id);
1897 continue;
1898 } else {
1899 // no special handling necessary for this item
1900 restoredRows.add(id);
1901 restored = false;
1902 }
1903 } else if (restored) {
1904 // Package is not yet available but might be
1905 // installed later.
1906 Launcher.addDumpLog(TAG,
1907 "package not yet restored: " + cn, true);
1908
1909 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 0) {
1910 // Restore has started once.
1911 } else if (installingPkgs.containsKey(cn.getPackageName())) {
1912 // App restore has started. Update the flag
1913 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1914 ContentValues values = new ContentValues();
1915 values.put(LauncherSettings.Favorites.RESTORED,
1916 promiseType);
1917 updateItem(id, values);
1918 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_TYPE🔵
1919 // This is a common app. Try to replace this.
1920 int appType = CommonAppTypeParser.decodeItemTypeFromFlag(🔵
1921 CommonAppTypeParser parser = new CommonAppTypeParser(id, 🔵
1922 if (parser.findDefaultApp()) {
1923 // Default app found. Replace it.
1924 intent = parser.parsedIntent;
1925 cn = intent.getComponent();
1926 ContentValues values = parser.parsedValues;
1927 values.put(LauncherSettings.Favorites.RESTORED, 0);
1928 updateItem(id, values);
1929 restored = false;
1930 itemReplaced = true;
1931
1932 } else if (REMOVE_UNRESTORED_ICONS) {
1933 Launcher.addDumpLog(TAG,
1934 "Unrestored package removed: " + cn, true);
1935 itemsToRemove.add(id);
1936 continue;
1937 }
1938 } else if (REMOVE_UNRESTORED_ICONS) {
1939 Launcher.addDumpLog(TAG,
1940 "Unrestored package removed: " + cn, true);
1941 itemsToRemove.add(id);
1942 continue;
1943 }
1944 } else if (launcherApps.isAppEnabled(
1945 manager, cn.getPackageName(),
1946 PackageManager.GET_UNINSTALLED_PACKAGES)) {
1947 // Package is present but not available.
1948 allowMissingTarget = true;
1949 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1950 } else if (!isSdCardReady) {
1951 // SdCard is not ready yet. Package might get available,
1952 // once it is ready.
1953 Launcher.addDumpLog(TAG, "Invalid package: " + cn
1954 + " (check again later)", true);
1955 HashSet<String> pkgs = sPendingPackages.get(user);
1956 if (pkgs == null) {
1957 pkgs = new HashSet<String>();
1958 sPendingPackages.put(user, pkgs);
1959 }
1960 pkgs.add(cn.getPackageName());
1961 allowMissingTarget = true;
1962 // Add the icon on the workspace anyway.
1963
1964 } else {
1965 // Do not wait for external media load anymore.
1966 // Log the invalid package, and remove it
1967 Launcher.addDumpLog(TAG,
1968 "Invalid package removed: " + cn, true);
1969 itemsToRemove.add(id);
1970 continue;
1971 }
1972 } else if (cn == null) {
1973 // For shortcuts with no component, keep them as they are
1974 restoredRows.add(id);
1975 restored = false;
1976 }
1977 } catch (URISyntaxException e) {
1978 Launcher.addDumpLog(TAG,
1979 "Invalid uri: " + intentDescription, true);
1980 continue;
1981 }
1982
1983 boolean useLowResIcon = container >= 0 &&
1984 c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW;
1985
1986 if (itemReplaced) {
1987 if (user.equals(UserHandleCompat.myUserHandle())) {
1988 info = getAppShortcutInfo(manager, intent, user, context, null,
1989 cursorIconInfo.iconIndex, titleIndex,
1990 false, useLowResIcon);
1991 } else {
1992 // Don't replace items for other profiles.
1993 itemsToRemove.add(id);
1994 continue;
1995 }
1996 } else if (restored) {
1997 if (user.equals(UserHandleCompat.myUserHandle())) {
1998 Launcher.addDumpLog(TAG,
1999 "constructing info for partially restored package",
2000 true);
2001 info = getRestoredItemInfo(c, titleIndex, intent,
2002 promiseType, itemType, cursorIconInfo, context);
2003 intent = getRestoredItemIntent(c, context, intent);
2004 } else {
2005 // Don't restore items for other profiles.
2006 itemsToRemove.add(id);
2007 continue;
2008 }
2009 } else if (itemType ==
2010 LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2011 info = getAppShortcutInfo(manager, intent, user, context, c,
2012 cursorIconInfo.iconIndex, titleIndex,
2013 allowMissingTarget, useLowResIcon);
2014 } else {
2015 info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
2016
2017 // App shortcuts that used to be automatically added to Launcher
2018 // didn't always have the correct intent flags set, so do that
2019 // here
2020 if (intent.getAction() != null &&
2021 intent.getCategories() != null &&
2022 intent.getAction().equals(Intent.ACTION_MAIN) &&
2023 intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
2024 intent.addFlags(
2025 Intent.FLAG_ACTIVITY_NEW_TASK |
2026 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
2027 }
2028 }
2029
2030 if (info != null) {
2031 info.id = id;
2032 info.intent = intent;
2033 info.container = container;
2034 info.screenId = c.getInt(screenIndex);
2035 info.cellX = c.getInt(cellXIndex);
2036 info.cellY = c.getInt(cellYIndex);
2037 info.rank = c.getInt(rankIndex);
2038 info.spanX = 1;
2039 info.spanY = 1;
2040 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
2041 if (info.promisedIntent != null) {
2042 info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber🔵
2043 }
2044 info.isDisabled = disabledState;
2045 if (isSafeMode && !Utilities.isSystemApp(context, intent)) {
2046 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
2047 }
2048
2049 // check & update map of what's occupied
2050 if (!checkItemPlacement(occupied, info)) {
2051 itemsToRemove.add(id);
2052 break;
2053 }
2054
2055 if (restored) {
2056 ComponentName cn = info.getTargetComponent();
2057 if (cn != null) {
2058 Integer progress = installingPkgs.get(cn.getPackageName());
2059 if (progress != null) {
2060 info.setInstallProgress(progress);
2061 } else {
2062 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
2063 }
2064 }
2065 }
2066
2067 switch (container) {
2068 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2069 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2070 sBgWorkspaceItems.add(info);
2071 break;
2072 default:
2073 // Item is in a user folder
2074 FolderInfo folderInfo =
2075 findOrMakeFolder(sBgFolders, container);
2076 folderInfo.add(info);
2077 break;
2078 }
2079 sBgItemsIdMap.put(info.id, info);
2080 } else {
2081 throw new RuntimeException("Unexpected null ShortcutInfo");
2082 }
2083 break;
2084
2085 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
2086 id = c.getLong(idIndex);
2087 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
2088
2089 // Do not trim the folder label, as is was set by the user.
2090 folderInfo.title = c.getString(titleIndex);
2091 folderInfo.id = id;
2092 folderInfo.container = container;
2093 folderInfo.screenId = c.getInt(screenIndex);
2094 folderInfo.cellX = c.getInt(cellXIndex);
2095 folderInfo.cellY = c.getInt(cellYIndex);
2096 folderInfo.spanX = 1;
2097 folderInfo.spanY = 1;
2098 folderInfo.options = c.getInt(optionsIndex);
2099
2100 // check & update map of what's occupied
2101 if (!checkItemPlacement(occupied, folderInfo)) {
2102 itemsToRemove.add(id);
2103 break;
2104 }
2105
2106 switch (container) {
2107 case LauncherSettings.Favorites.CONTAINER_DESKTOP:
2108 case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
2109 sBgWorkspaceItems.add(folderInfo);
2110 break;
2111 }
2112
2113 if (restored) {
2114 // no special handling required for restored folders
2115 restoredRows.add(id);
2116 }
2117
2118 sBgItemsIdMap.put(folderInfo.id, folderInfo);
2119 sBgFolders.put(folderInfo.id, folderInfo);
2120 break;
2121
2122 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2123 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
2124 // Read all Launcher-specific widget details
2125 boolean customWidget = itemType ==
2126 LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
2127
2128 id = c.getLong(idIndex);
2129 int appWidgetId = c.getInt(appWidgetIdIndex);
2130 serialNumber= c.getLong(profileIdIndex);
2131 String savedProvider = c.getString(appWidgetProviderIndex);
2132 id = c.getLong(idIndex);
2133 user = allUsers.get(serialNumber);
2134 if (user == null) {
2135 itemsToRemove.add(id);
2136 continue;
2137 }
2138 <<<<<<< MINE
2139
2140 ||||||| BASE
2141 id = c.getLong(idIndex);
2142 final ComponentName component =
2143 ComponentName.unflattenFromString(savedProvider);
2144
2145 final int restoreStatus = c.getInt(restoredIndex);
2146 final boolean isIdValid = (restoreStatus &
2147 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
2148
2149 final boolean wasProviderReady = (restoreStatus &
2150 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
2151
2152 final LauncherAppWidgetProviderInfo provider =
2153 LauncherModel.getProviderInfo(context,
2154 ComponentName.unflattenFromString(savedProvider),
2155 user);
2156
2157 final boolean isProviderReady = isValidProvider(provider);
2158 if (!isSafeMode && !customWidget &&
2159 wasProviderReady && !isProviderReady) {
2160 String log = "Deleting widget that isn't installed anymore: "
2161 + "id=" + id + " appWidgetId=" + appWidgetId;
2162
2163 Log.e(TAG, log);
2164 Launcher.addDumpLog(TAG, log, false);
2165 itemsToRemove.add(id);
2166 } else {
2167 if (isProviderReady) {
2168 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2169 provider.provider);
2170
2171 if (!customWidget) {
2172 =======
2173 String savedProvider = c.getString(appWidgetProviderIndex);
2174 >>>>>>> YOURS
2175 final ComponentName component =
2176 ComponentName.unflattenFromString(savedProvider);
2177
2178 final int restoreStatus = c.getInt(restoredIndex);
2179 final boolean isIdValid = (restoreStatus &
2180 LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0;
2181 final boolean wasProviderReady = (restoreStatus &
2182 LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0;
2183
2184 final LauncherAppWidgetProviderInfo provider =
2185 LauncherModel.getProviderInfo(context,
2186 ComponentName.unflattenFromString(savedProvider),
2187 user);
2188
2189 final boolean isProviderReady = isValidProvider(provider);
2190 if (!isSafeMode && !customWidget &&
2191 wasProviderReady && !isProviderReady) {
2192 String log = "Deleting widget that isn't installed anymore: "
2193 + "id=" + id + " appWidgetId=" + appWidgetId;
2194
2195 Log.e(TAG, log);
2196 Launcher.addDumpLog(TAG, log, false);
2197 itemsToRemove.add(id);
2198 } else {
2199 if (isProviderReady) {
2200 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2201 provider.provider);
2202
2203 int status = restoreStatus;
2204 if (!wasProviderReady) {
2205 // If provider was not previously ready, update the
2206 // status and UI flag.
2207
2208 // Id would be valid only if the widget restore broadcast was🔵
2209 if (isIdValid) {
2210 status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
2211 } else {
2212 status &= ~LauncherAppWidgetInfo
2213 .FLAG_PROVIDER_NOT_READY;
2214 }
2215 }
2216 appWidgetInfo.restoreStatus = status;
2217 } else {
2218 Log.v(TAG, "Widget restore pending id=" + id
2219 + " appWidgetId=" + appWidgetId
2220 + " status =" + restoreStatus);
2221 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
2222 component);
2223 appWidgetInfo.restoreStatus = restoreStatus;
2224 Integer installProgress = installingPkgs.get(component.getPackage🔵
2225
2226 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) 🔵
2227 // Restore has started once.
2228 } else if (installProgress != null) {
2229 // App restore has started. Update the flag
2230 appWidgetInfo.restoreStatus |=
2231 LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
2232 } else if (REMOVE_UNRESTORED_ICONS && !isSafeMode) {
2233 Launcher.addDumpLog(TAG,
2234 "Unrestored widget removed: " + component, true);
2235 itemsToRemove.add(id);
2236 continue;
2237 }
2238
2239 appWidgetInfo.installProgress =
2240 installProgress == null ? 0 : installProgress;
2241 }
2242
2243 appWidgetInfo.id = id;
2244 appWidgetInfo.screenId = c.getInt(screenIndex);
2245 appWidgetInfo.cellX = c.getInt(cellXIndex);
2246 appWidgetInfo.cellY = c.getInt(cellYIndex);
2247 appWidgetInfo.spanX = c.getInt(spanXIndex);
2248 appWidgetInfo.spanY = c.getInt(spanYIndex);
2249
2250 if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2251 container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2252 Log.e(TAG, "Widget found where container != " +
2253 "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
2254 continue;
2255 }
2256
2257 appWidgetInfo.container = container;
2258 // check & update map of what's occupied
2259 if (!checkItemPlacement(occupied, appWidgetInfo)) {
2260 itemsToRemove.add(id);
2261 break;
2262 }
2263
2264 if (!customWidget) {
2265 String providerName =
2266 appWidgetInfo.providerName.flattenToString();
2267 if (!providerName.equals(savedProvider) ||
2268 (appWidgetInfo.restoreStatus != restoreStatus)) {
2269 ContentValues values = new ContentValues();
2270 values.put(
2271 LauncherSettings.Favorites.APPWIDGET_PROVIDER,
2272 providerName);
2273 values.put(LauncherSettings.Favorites.RESTORED,
2274 appWidgetInfo.restoreStatus);
2275 updateItem(id, values);
2276 }
2277 }
2278 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2279 sBgAppWidgets.add(appWidgetInfo);
2280 }
2281 break;
2282 }
2283 } catch (Exception e) {
2284 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2285 }
2286 }
2287 } finally {
2288 if (c != null) {
2289 c.close();
2290 }
2291 }
2292
2293 // Break early if we've stopped loading
2294 if (mStopped) {
2295 clearSBgDataStructures();
2296 return;
2297 }
2298
2299 if (itemsToRemove.size() > 0) {
2300 // Remove dead items
2301 contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI,
2302 Utilities.createDbSelectionQuery(
2303 LauncherSettings.Favorites._ID, itemsToRemove), null);
2304 if (DEBUG_LOADERS) {
2305 Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(
2306 LauncherSettings.Favorites._ID, itemsToRemove));
2307 }
2308
2309 // Remove any empty folder
2310 for (long folderId : LauncherAppState.getLauncherProvider()
2311 .deleteEmptyFolders()) {
2312 sBgWorkspaceItems.remove(sBgFolders.get(folderId));
2313 sBgFolders.remove(folderId);
2314 sBgItemsIdMap.remove(folderId);
2315 }
2316 }
2317
2318 if (restoredRows.size() > 0) {
2319 // Update restored items that no longer require special handling
2320 ContentValues values = new ContentValues();
2321 values.put(LauncherSettings.Favorites.RESTORED, 0);
2322 contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
2323 Utilities.createDbSelectionQuery(
2324 LauncherSettings.Favorites._ID, restoredRows), null);
2325 }
2326
2327 if (!isSdCardReady && !sPendingPackages.isEmpty()) {
2328 context.registerReceiver(new AppsAvailabilityCheck(),
2329 new IntentFilter(StartupReceiver.SYSTEM_READY),
2330 null, sWorker);
2331 }
2332
2333 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
2334
2335 // Remove any empty screens
2336 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2337 for (ItemInfo item: sBgItemsIdMap) {
2338 long screenId = item.screenId;
2339 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2340 unusedScreens.contains(screenId)) {
2341 unusedScreens.remove(screenId);
2342 }
2343 }
2344
2345 // If there are any empty screens remove them, and update.
2346 if (unusedScreens.size() != 0) {
2347 sBgWorkspaceScreens.removeAll(unusedScreens);
2348 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2349 }
2350
2351 if (DEBUG_LOADERS) {
2352 Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
2353 Log.d(TAG, "workspace layout: ");
2354 int nScreens = occupied.size();
2355 for (int y = 0; y < countY; y++) {
2356 String line = "";
2357
2358 for (int i = 0; i < nScreens; i++) {
2359 long screenId = occupied.keyAt(i);
2360 if (screenId > 0) {
2361 line += " | ";
2362 }
2363 ItemInfo[][] screen = occupied.valueAt(i);
2364 for (int x = 0; x < countX; x++) {
2365 if (x < screen.length && y < screen[x].length) {
2366 line += (screen[x][y] != null) ? "#" : ".";
2367 } else {
2368 line += "!";
2369 }
2370 }
2371 }
2372 Log.d(TAG, "[ " + line + " ]");
2373 }
2374 }
2375 }
2376 }
2377
2378 /**
2379 * Partially updates the item without any notification. Must be called on the worker thread.
2380 */
2381 private void updateItem(long itemId, ContentValues update) {
2382 mContext.getContentResolver().update(
2383 LauncherSettings.Favorites.CONTENT_URI,
2384 update,
2385 BaseColumns._ID + "= ?",
2386 new String[]{Long.toString(itemId)});
2387 }
2388
2389 /** Filters the set of items who are directly or indirectly (via another container) on the
2390 * specified screen. */
2391 private void filterCurrentWorkspaceItems(long currentScreenId,
2392 ArrayList<ItemInfo> allWorkspaceItems,
2393 ArrayList<ItemInfo> currentScreenItems,
2394 ArrayList<ItemInfo> otherScreenItems) {
2395 // Purge any null ItemInfos
2396 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2397 while (iter.hasNext()) {
2398 ItemInfo i = iter.next();
2399 if (i == null) {
2400 iter.remove();
2401 }
2402 }
2403
2404 // Order the set of items by their containers first, this allows use to walk through the
2405 // list sequentially, build up a list of containers that are in the specified screen,
2406 // as well as all items in those containers.
2407 Set<Long> itemsOnScreen = new HashSet<Long>();
2408 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2409 @Override
2410 public int compare(ItemInfo lhs, ItemInfo rhs) {
2411 return (int) (lhs.container - rhs.container);
2412 }
2413 });
2414 for (ItemInfo info : allWorkspaceItems) {
2415 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2416 if (info.screenId == currentScreenId) {
2417 currentScreenItems.add(info);
2418 itemsOnScreen.add(info.id);
2419 } else {
2420 otherScreenItems.add(info);
2421 }
2422 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2423 currentScreenItems.add(info);
2424 itemsOnScreen.add(info.id);
2425 } else {
2426 if (itemsOnScreen.contains(info.container)) {
2427 currentScreenItems.add(info);
2428 itemsOnScreen.add(info.id);
2429 } else {
2430 otherScreenItems.add(info);
2431 }
2432 }
2433 }
2434 }
2435
2436 /** Filters the set of widgets which are on the specified screen. */
2437 private void filterCurrentAppWidgets(long currentScreenId,
2438 ArrayList<LauncherAppWidgetInfo> appWidgets,
2439 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2440 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2441
2442 for (LauncherAppWidgetInfo widget : appWidgets) {
2443 if (widget == null) continue;
2444 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2445 widget.screenId == currentScreenId) {
2446 currentScreenWidgets.add(widget);
2447 } else {
2448 otherScreenWidgets.add(widget);
2449 }
2450 }
2451 }
2452
2453 /** Filters the set of folders which are on the specified screen. */
2454 private void filterCurrentFolders(long currentScreenId,
2455 LongArrayMap<ItemInfo> itemsIdMap,
2456 LongArrayMap<FolderInfo> folders,
2457 LongArrayMap<FolderInfo> currentScreenFolders,
2458 LongArrayMap<FolderInfo> otherScreenFolders) {
2459
2460 int total = folders.size();
2461 for (int i = 0; i < total; i++) {
2462 long id = folders.keyAt(i);
2463 FolderInfo folder = folders.valueAt(i);
2464
2465 ItemInfo info = itemsIdMap.get(id);
2466 if (info == null || folder == null) continue;
2467 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2468 info.screenId == currentScreenId) {
2469 currentScreenFolders.put(id, folder);
2470 } else {
2471 otherScreenFolders.put(id, folder);
2472 }
2473 }
2474 }
2475
2476 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2477 * right) */
2478 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2479 final LauncherAppState app = LauncherAppState.getInstance();
2480 final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
2481 // XXX: review this
2482 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2483 @Override
2484 public int compare(ItemInfo lhs, ItemInfo rhs) {
2485 int cellCountX = (int) profile.numColumns;
2486 int cellCountY = (int) profile.numRows;
2487 int screenOffset = cellCountX * cellCountY;
2488 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1); // +1 hotseat
2489 long lr = (lhs.container * containerOffset + lhs.screenId * screenOffset +
2490 lhs.cellY * cellCountX + lhs.cellX);
2491 long rr = (rhs.container * containerOffset + rhs.screenId * screenOffset +
2492 rhs.cellY * cellCountX + rhs.cellX);
2493 return (int) (lr - rr);
2494 }
2495 });
2496 }
2497
2498 private void bindWorkspaceScreens(final Callbacks oldCallbacks,
2499 final ArrayList<Long> orderedScreens) {
2500 final Runnable r = new Runnable() {
2501 @Override
2502 public void run() {
2503 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2504 if (callbacks != null) {
2505 callbacks.bindScreens(orderedScreens);
2506 }
2507 }
2508 };
2509 runOnMainThread(r);
2510 }
2511
2512 private void bindWorkspaceItems(final Callbacks oldCallbacks,
2513 final ArrayList<ItemInfo> workspaceItems,
2514 final ArrayList<LauncherAppWidgetInfo> appWidgets,
2515 final LongArrayMap<FolderInfo> folders,
2516 ArrayList<Runnable> deferredBindRunnables) {
2517
2518 final boolean postOnMainThread = (deferredBindRunnables != null);
2519
2520 // Bind the workspace items
2521 int N = workspaceItems.size();
2522 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2523 final int start = i;
2524 final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
2525 final Runnable r = new Runnable() {
2526 @Override
2527 public void run() {
2528 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2529 if (callbacks != null) {
2530 callbacks.bindItems(workspaceItems, start, start+chunkSize,
2531 false);
2532 }
2533 }
2534 };
2535 if (postOnMainThread) {
2536 synchronized (deferredBindRunnables) {
2537 deferredBindRunnables.add(r);
2538 }
2539 } else {
2540 runOnMainThread(r);
2541 }
2542 }
2543
2544 // Bind the folders
2545 if (!folders.isEmpty()) {
2546 final Runnable r = new Runnable() {
2547 public void run() {
2548 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2549 if (callbacks != null) {
2550 callbacks.bindFolders(folders);
2551 }
2552 }
2553 };
2554 if (postOnMainThread) {
2555 synchronized (deferredBindRunnables) {
2556 deferredBindRunnables.add(r);
2557 }
2558 } else {
2559 runOnMainThread(r);
2560 }
2561 }
2562
2563 // Bind the widgets, one at a time
2564 N = appWidgets.size();
2565 for (int i = 0; i < N; i++) {
2566 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2567 final Runnable r = new Runnable() {
2568 public void run() {
2569 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2570 if (callbacks != null) {
2571 callbacks.bindAppWidget(widget);
2572 }
2573 }
2574 };
2575 if (postOnMainThread) {
2576 deferredBindRunnables.add(r);
2577 } else {
2578 runOnMainThread(r);
2579 }
2580 }
2581 }
2582
2583 /**
2584 * Binds all loaded data to actual views on the main thread.
2585 */
2586 private void bindWorkspace(int synchronizeBindPage) {
2587 final long t = SystemClock.uptimeMillis();
2588 Runnable r;
2589
2590 // Don't use these two variables in any of the callback runnables.
2591 // Otherwise we hold a reference to them.
2592 final Callbacks oldCallbacks = mCallbacks.get();
2593 if (oldCallbacks == null) {
2594 // This launcher has exited and nobody bothered to tell us. Just bail.
2595 Log.w(TAG, "LoaderTask running with no launcher");
2596 return;
2597 }
2598
2599 // Save a copy of all the bg-thread collections
2600 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2601 ArrayList<LauncherAppWidgetInfo> appWidgets =
2602 new ArrayList<LauncherAppWidgetInfo>();
2603 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2604
2605 final LongArrayMap<FolderInfo> folders;
2606 final LongArrayMap<ItemInfo> itemsIdMap;
2607
2608 synchronized (sBgLock) {
2609 workspaceItems.addAll(sBgWorkspaceItems);
2610 appWidgets.addAll(sBgAppWidgets);
2611 orderedScreenIds.addAll(sBgWorkspaceScreens);
2612
2613 folders = sBgFolders.clone();
2614 itemsIdMap = sBgItemsIdMap.clone();
2615 }
2616
2617 final boolean isLoadingSynchronously =
2618 synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2619 int currScreen = isLoadingSynchronously ? synchronizeBindPage :
2620 oldCallbacks.getCurrentWorkspaceScreen();
2621 if (currScreen >= orderedScreenIds.size()) {
2622 // There may be no workspace screens (just hotseat items and an empty page).
2623 currScreen = PagedView.INVALID_RESTORE_PAGE;
2624 }
2625 final int currentScreen = currScreen;
2626 final long currentScreenId = currentScreen < 0
2627 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);
2628
2629 // Load all the items that are on the current page first (and in the process, unbind
2630 // all the existing workspace items before we call startBinding() below.
2631 unbindWorkspaceItemsOnMainThread();
2632
2633 // Separate the items that are on the current screen, and all the other remaining items
2634 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2635 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2636 ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
2637 new ArrayList<LauncherAppWidgetInfo>();
2638 ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
2639 new ArrayList<LauncherAppWidgetInfo>();
2640 LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
2641 LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
2642
2643 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
2644 otherWorkspaceItems);
2645 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
2646 otherAppWidgets);
2647 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
2648 otherFolders);
2649 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2650 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2651
2652 // Tell the workspace that we're about to start binding items
2653 r = new Runnable() {
2654 public void run() {
2655 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2656 if (callbacks != null) {
2657 callbacks.startBinding();
2658 }
2659 }
2660 };
2661 runOnMainThread(r);
2662
2663 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2664
2665 // Load items on the current page
2666 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
2667 currentFolders, null);
2668 if (isLoadingSynchronously) {
2669 r = new Runnable() {
2670 public void run() {
2671 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2672 if (callbacks != null && currentScreen != PagedView.INVALID_RESTORE_PAGE) {
2673 callbacks.onPageBoundSynchronously(currentScreen);
2674 }
2675 }
2676 };
2677 runOnMainThread(r);
2678 }
2679
2680 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2681 // work until after the first render)
2682 synchronized (mDeferredBindRunnables) {
2683 mDeferredBindRunnables.clear();
2684 }
2685 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
2686 (isLoadingSynchronously ? mDeferredBindRunnables : null));
2687
2688 // Tell the workspace that we're done binding items
2689 r = new Runnable() {
2690 public void run() {
2691 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2692 if (callbacks != null) {
2693 callbacks.finishBindingItems();
2694 }
2695
2696 // If we're profiling, ensure this is the last thing in the queue.
2697 if (DEBUG_LOADERS) {
2698 Log.d(TAG, "bound workspace in "
2699 + (SystemClock.uptimeMillis()-t) + "ms");
2700 }
2701
2702 mIsLoadingAndBindingWorkspace = false;
2703 }
2704 };
2705 if (isLoadingSynchronously) {
2706 synchronized (mDeferredBindRunnables) {
2707 mDeferredBindRunnables.add(r);
2708 }
2709 } else {
2710 runOnMainThread(r);
2711 }
2712 }
2713
2714 private void loadAndBindAllApps() {
2715 if (DEBUG_LOADERS) {
2716 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2717 }
2718 if (!mAllAppsLoaded) {
2719 loadAllApps();
2720 synchronized (LoaderTask.this) {
2721 if (mStopped) {
2722 return;
2723 }
2724 }
2725 updateIconCache();
2726 synchronized (LoaderTask.this) {
2727 if (mStopped) {
2728 return;
2729 }
2730 mAllAppsLoaded = true;
2731 }
2732 } else {
2733 onlyBindAllApps();
2734 }
2735 }
2736
2737 private void updateIconCache() {
2738 // Ignore packages which have a promise icon.
2739 HashSet<String> packagesToIgnore = new HashSet<>();
2740 synchronized (sBgLock) {
2741 for (ItemInfo info : sBgItemsIdMap) {
2742 if (info instanceof ShortcutInfo) {
2743 ShortcutInfo si = (ShortcutInfo) info;
2744 if (si.isPromise() && si.getTargetComponent() != null) {
2745 packagesToIgnore.add(si.getTargetComponent().getPackageName());
2746 }
2747 } else if (info instanceof LauncherAppWidgetInfo) {
2748 LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
2749 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2750 packagesToIgnore.add(lawi.providerName.getPackageName());
2751 }
2752 }
2753 }
2754 }
2755 mIconCache.updateDbIcons(packagesToIgnore);
2756 }
2757
2758 private void onlyBindAllApps() {
2759 final Callbacks oldCallbacks = mCallbacks.get();
2760 if (oldCallbacks == null) {
2761 // This launcher has exited and nobody bothered to tell us. Just bail.
2762 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2763 return;
2764 }
2765
2766 // shallow copy
2767 @SuppressWarnings("unchecked")
2768 final ArrayList<AppInfo> list
2769 = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
2770 final WidgetsModel widgetList = mBgWidgetsModel.clone();
2771 Runnable r = new Runnable() {
2772 public void run() {
2773 final long t = SystemClock.uptimeMillis();
2774 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2775 if (callbacks != null) {
2776 callbacks.bindAllApplications(list);
2777 callbacks.bindAllPackages(widgetList);
2778 }
2779 if (DEBUG_LOADERS) {
2780 Log.d(TAG, "bound all " + list.size() + " apps from cache in "
2781 + (SystemClock.uptimeMillis()-t) + "ms");
2782 }
2783 }
2784 };
2785 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2786 if (isRunningOnMainThread) {
2787 r.run();
2788 } else {
2789 mHandler.post(r);
2790 }
2791 }
2792
2793 private void loadAllApps() {
2794 final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2795
2796 final Callbacks oldCallbacks = mCallbacks.get();
2797 if (oldCallbacks == null) {
2798 // This launcher has exited and nobody bothered to tell us. Just bail.
2799 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2800 return;
2801 }
2802
2803 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2804
2805 // Clear the list of apps
2806 mBgAllAppsList.clear();
2807 for (UserHandleCompat user : profiles) {
2808 // Query for the set of apps
2809 final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
2810 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2811 if (DEBUG_LOADERS) {
2812 Log.d(TAG, "getActivityList took "
2813 + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
2814 Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
2815 }
2816 // Fail if we don't have any apps
2817 // TODO: Fix this. Only fail for the current user.
2818 if (apps == null || apps.isEmpty()) {
2819 return;
2820 }
2821
2822 // Create the ApplicationInfos
2823 for (int i = 0; i < apps.size(); i++) {
2824 LauncherActivityInfoCompat app = apps.get(i);
2825 // This builds the icon bitmaps.
2826 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
2827 }
2828
2829 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2830 if (heuristic != null) {
2831 runAfterBindCompletes(new Runnable() {
2832
2833 @Override
2834 public void run() {
2835 heuristic.processUserApps(apps);
2836 }
2837 });
2838 }
2839 }
2840 // Huh? Shouldn't this be inside the Runnable below?
2841 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2842 mBgAllAppsList.added = new ArrayList<AppInfo>();
2843
2844 // Post callback on main thread
2845 mHandler.post(new Runnable() {
2846 public void run() {
2847
2848 final long bindTime = SystemClock.uptimeMillis();
2849 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2850 if (callbacks != null) {
2851 callbacks.bindAllApplications(added);
2852 if (DEBUG_LOADERS) {
2853 Log.d(TAG, "bound " + added.size() + " apps in "
2854 + (SystemClock.uptimeMillis() - bindTime) + "ms");
2855 }
2856 } else {
2857 Log.i(TAG, "not binding apps: no Launcher activity");
2858 }
2859 }
2860 });
2861 // Cleanup any data stored for a deleted user.
2862 ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2863
2864 loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks),
2865 true /* refresh */);
2866 if (DEBUG_LOADERS) {
2867 Log.d(TAG, "Icons processed in "
2868 + (SystemClock.uptimeMillis() - loadTime) + "ms");
2869 }
2870 }
2871
2872 public void dumpState() {
2873 synchronized (sBgLock) {
2874 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2875 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2876 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2877 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2878 }
2879 }
2880 }
2881
2882 /**
2883 * Called when the icons for packages have been updated in the icon cache.
2884 */
2885 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
2886 final Callbacks callbacks = getCallback();
2887 final ArrayList<AppInfo> updatedApps = new ArrayList<>();
2888 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
2889
2890 // If any package icon has changed (app was updated while launcher was dead),
2891 // update the corresponding shortcuts.
2892 synchronized (sBgLock) {
2893 for (ItemInfo info : sBgItemsIdMap) {
2894 if (info instanceof ShortcutInfo && user.equals(info.user)
2895 && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
2896 ShortcutInfo si = (ShortcutInfo) info;
2897 ComponentName cn = si.getTargetComponent();
2898 if (cn != null && updatedPackages.contains(cn.getPackageName())) {
2899 si.updateIcon(mIconCache);
2900 updatedShortcuts.add(si);
2901 }
2902 }
2903 }
2904 mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
2905 }
2906
2907 if (!updatedShortcuts.isEmpty()) {
2908 final UserHandleCompat userFinal = user;
2909 mHandler.post(new Runnable() {
2910
2911 public void run() {
2912 Callbacks cb = getCallback();
2913 if (cb != null && callbacks == cb) {
2914 cb.bindShortcutsChanged(updatedShortcuts,
2915 new ArrayList<ShortcutInfo>(), userFinal);
2916 }
2917 }
2918 });
2919 }
2920
2921 if (!updatedApps.isEmpty()) {
2922 mHandler.post(new Runnable() {
2923
2924 public void run() {
2925 Callbacks cb = getCallback();
2926 if (cb != null && callbacks == cb) {
2927 cb.bindAppsUpdated(updatedApps);
2928 }
2929 }
2930 });
2931 }
2932
2933 // Reload widget list. No need to refresh, as we only want to update the icons and labels.
2934 loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, false);
2935 }
2936
2937 void enqueuePackageUpdated(PackageUpdatedTask task) {
2938 sWorker.post(task);
2939 }
2940
2941 @Thunk class AppsAvailabilityCheck extends BroadcastReceiver {
2942
2943 @Override
2944 public void onReceive(Context context, Intent intent) {
2945 synchronized (sBgLock) {
2946 final LauncherAppsCompat launcherApps = LauncherAppsCompat
2947 .getInstance(mApp.getContext());
2948 final PackageManager manager = context.getPackageManager();
2949 final ArrayList<String> packagesRemoved = new ArrayList<String>();
2950 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
2951 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
2952 UserHandleCompat user = entry.getKey();
2953 packagesRemoved.clear();
2954 packagesUnavailable.clear();
2955 for (String pkg : entry.getValue()) {
2956 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
2957 boolean packageOnSdcard = launcherApps.isAppEnabled(
2958 manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
2959 if (packageOnSdcard) {
2960 Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
2961 packagesUnavailable.add(pkg);
2962 } else {
2963 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
2964 packagesRemoved.add(pkg);
2965 }
2966 }
2967 }
2968 if (!packagesRemoved.isEmpty()) {
2969 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
2970 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
2971 }
2972 if (!packagesUnavailable.isEmpty()) {
2973 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
2974 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user🔵
2975 }
2976 }
2977 sPendingPackages.clear();
2978 }
2979 }
2980 }
2981
2982 private class PackageUpdatedTask implements Runnable {
2983 int mOp;
2984 String[] mPackages;
2985 UserHandleCompat mUser;
2986
2987 public static final int OP_NONE = 0;
2988 public static final int OP_ADD = 1;
2989 public static final int OP_UPDATE = 2;
2990 public static final int OP_REMOVE = 3; // uninstlled
2991 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2992
2993
2994 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
2995 mOp = op;
2996 mPackages = packages;
2997 mUser = user;
2998 }
2999
3000 public void run() {
3001 if (!mHasLoaderCompletedOnce) {
3002 // Loader has not yet run.
3003 return;
3004 }
3005 final Context context = mApp.getContext();
3006
3007 final String[] packages = mPackages;
3008 final int N = packages.length;
3009 switch (mOp) {
3010 case OP_ADD: {
3011 for (int i=0; i<N; i++) {
3012 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
3013 mIconCache.updateIconsForPkg(packages[i], mUser);
3014 mBgAllAppsList.addPackage(context, packages[i], mUser);
3015 }
3016
3017 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3018 if (heuristic != null) {
3019 heuristic.processPackageAdd(mPackages);
3020 }
3021 break;
3022 }
3023 case OP_UPDATE:
3024 for (int i=0; i<N; i++) {
3025 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
3026 mIconCache.updateIconsForPkg(packages[i], mUser);
3027 mBgAllAppsList.updatePackage(context, packages[i], mUser);
3028 mApp.getWidgetCache().removePackage(packages[i], mUser);
3029 }
3030 break;
3031 case OP_REMOVE: {
3032 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
3033 if (heuristic != null) {
3034 heuristic.processPackageRemoved(mPackages);
3035 }
3036 for (int i=0; i<N; i++) {
3037 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3038 mIconCache.removeIconsForPkg(packages[i], mUser);
3039 }
3040 // Fall through
3041 }
3042 case OP_UNAVAILABLE:
3043 for (int i=0; i<N; i++) {
3044 if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
3045 mBgAllAppsList.removePackage(packages[i], mUser);
3046 mApp.getWidgetCache().removePackage(packages[i], mUser);
3047 }
3048 break;
3049 }
3050
3051 ArrayList<AppInfo> added = null;
3052 ArrayList<AppInfo> modified = null;
3053 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
3054
3055 if (mBgAllAppsList.added.size() > 0) {
3056 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
3057 mBgAllAppsList.added.clear();
3058 }
3059 if (mBgAllAppsList.modified.size() > 0) {
3060 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
3061 mBgAllAppsList.modified.clear();
3062 }
3063 if (mBgAllAppsList.removed.size() > 0) {
3064 removedApps.addAll(mBgAllAppsList.removed);
3065 mBgAllAppsList.removed.clear();
3066 }
3067
3068 final Callbacks callbacks = getCallback();
3069 if (callbacks == null) {
3070 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
3071 return;
3072 }
3073
3074 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps =
3075 new HashMap<ComponentName, AppInfo>();
3076
3077 if (added != null) {
3078 addAppsToAllApps(context, added);
3079 for (AppInfo ai : added) {
3080 addedOrUpdatedApps.put(ai.componentName, ai);
3081 }
3082 }
3083
3084 if (modified != null) {
3085 final ArrayList<AppInfo> modifiedFinal = modified;
3086 for (AppInfo ai : modified) {
3087 addedOrUpdatedApps.put(ai.componentName, ai);
3088 }
3089
3090 mHandler.post(new Runnable() {
3091 public void run() {
3092 Callbacks cb = getCallback();
3093 if (callbacks == cb && cb != null) {
3094 callbacks.bindAppsUpdated(modifiedFinal);
3095 }
3096 }
3097 });
3098 }
3099
3100 // Update shortcut infos
3101 if (mOp == OP_ADD || mOp == OP_UPDATE) {
3102 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
3103 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
3104 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
3105
3106 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
3107 synchronized (sBgLock) {
3108 for (ItemInfo info : sBgItemsIdMap) {
3109 if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
3110 ShortcutInfo si = (ShortcutInfo) info;
3111 boolean infoUpdated = false;
3112 boolean shortcutUpdated = false;
3113
3114 // Update shortcuts which use iconResource.
3115 if ((si.iconResource != null)
3116 && packageSet.contains(si.iconResource.packageName)) {
3117 Bitmap icon = Utilities.createIconBitmap(
3118 si.iconResource.packageName,
3119 si.iconResource.resourceName, context);
3120 if (icon != null) {
3121 si.setIcon(icon);
3122 si.usingFallbackIcon = false;
3123 infoUpdated = true;
3124 }
3125 }
3126
3127 ComponentName cn = si.getTargetComponent();
3128 if (cn != null && packageSet.contains(cn.getPackageName())) {
3129 AppInfo appInfo = addedOrUpdatedApps.get(cn);
3130
3131 if (si.isPromise()) {
3132 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
3133 // Auto install icon
3134 PackageManager pm = context.getPackageManager();
3135 ResolveInfo matched = pm.resolveActivity(
3136 new Intent(Intent.ACTION_MAIN)
3137 .setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
3138 PackageManager.MATCH_DEFAULT_ONLY);
3139 if (matched == null) {
3140 // Try to find the best match activity.
3141 Intent intent = pm.getLaunchIntentForPackage(
3142 cn.getPackageName());
3143 if (intent != null) {
3144 cn = intent.getComponent();
3145 appInfo = addedOrUpdatedApps.get(cn);
3146 }
3147
3148 if ((intent == null) || (appInfo == null)) {
3149 removedShortcuts.add(si);
3150 continue;
3151 }
3152 si.promisedIntent = intent;
3153 }
3154 }
3155
3156 // Restore the shortcut.
3157 if (appInfo != null) {
3158 si.flags = appInfo.flags;
3159 }
3160
3161 si.intent = si.promisedIntent;
3162 si.promisedIntent = null;
3163 si.status = ShortcutInfo.DEFAULT;
3164 infoUpdated = true;
3165 si.updateIcon(mIconCache);
3166 }
3167
3168 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
3169 && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATIO🔵
3170 si.updateIcon(mIconCache);
3171 si.title = Utilities.trim(appInfo.title);
3172 si.contentDescription = appInfo.contentDescription;
3173 infoUpdated = true;
3174 }
3175
3176 if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
3177 // Since package was just updated, the target must be available now.
3178 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3179 shortcutUpdated = true;
3180 }
3181 }
3182
3183 if (infoUpdated || shortcutUpdated) {
3184 updatedShortcuts.add(si);
3185 }
3186 if (infoUpdated) {
3187 updateItemInDatabase(context, si);
3188 }
3189 } else if (info instanceof LauncherAppWidgetInfo) {
3190 LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
3191 if (mUser.equals(widgetInfo.user)
3192 && widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_🔵
3193 && packageSet.contains(widgetInfo.providerName.getPackageName())) {
3194 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
3195 widgets.add(widgetInfo);
3196 updateItemInDatabase(context, widgetInfo);
3197 }
3198 }
3199 }
3200 }
3201
3202 if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
3203 mHandler.post(new Runnable() {
3204
3205 public void run() {
3206 Callbacks cb = getCallback();
3207 if (callbacks == cb && cb != null) {
3208 callbacks.bindShortcutsChanged(
3209 updatedShortcuts, removedShortcuts, mUser);
3210 }
3211 }
3212 });
3213 if (!removedShortcuts.isEmpty()) {
3214 deleteItemsFromDatabase(context, removedShortcuts);
3215 }
3216 }
3217 if (!widgets.isEmpty()) {
3218 mHandler.post(new Runnable() {
3219 public void run() {
3220 Callbacks cb = getCallback();
3221 if (callbacks == cb && cb != null) {
3222 callbacks.bindWidgetsRestored(widgets);
3223 }
3224 }
3225 });
3226 }
3227 }
3228
3229 final ArrayList<String> removedPackageNames =
3230 new ArrayList<String>();
3231 if (mOp == OP_REMOVE || mOp == OP_UNAVAILABLE) {
3232 // Mark all packages in the broadcast to be removed
3233 removedPackageNames.addAll(Arrays.asList(packages));
3234 } else if (mOp == OP_UPDATE) {
3235 // Mark disabled packages in the broadcast to be removed
3236 for (int i=0; i<N; i++) {
3237 if (isPackageDisabled(context, packages[i], mUser)) {
3238 removedPackageNames.add(packages[i]);
3239 }
3240 }
3241 }
3242
3243 if (!removedPackageNames.isEmpty() || !removedApps.isEmpty()) {
3244 final int removeReason;
3245 if (mOp == OP_UNAVAILABLE) {
3246 removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
3247 } else {
3248 // Remove all the components associated with this package
3249 for (String pn : removedPackageNames) {
3250 deletePackageFromDatabase(context, pn, mUser);
3251 }
3252 // Remove all the specific components
3253 for (AppInfo a : removedApps) {
3254 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
3255 deleteItemsFromDatabase(context, infos);
3256 }
3257 removeReason = 0;
3258 }
3259
3260 // Remove any queued items from the install queue
3261 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
3262 // Call the components-removed callback
3263 mHandler.post(new Runnable() {
3264 public void run() {
3265 Callbacks cb = getCallback();
3266 if (callbacks == cb && cb != null) {
3267 callbacks.bindComponentsRemoved(
3268 removedPackageNames, removedApps, mUser, removeReason);
3269 }
3270 }
3271 });
3272 }
3273
3274 // onProvidersChanged method (API >= 17) already refreshed the widget list
3275 loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
3276
3277 // Write all the logs to disk
3278 mHandler.post(new Runnable() {
3279 public void run() {
3280 Callbacks cb = getCallback();
3281 if (callbacks == cb && cb != null) {
3282 callbacks.dumpLogsToLocalData();
3283 }
3284 }
3285 });
3286 }
3287 }
3288
3289 public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context,
3290 boolean refresh) {
3291 ArrayList<LauncherAppWidgetProviderInfo> results =
3292 new ArrayList<LauncherAppWidgetProviderInfo>();
3293 try {
3294 synchronized (sBgLock) {
3295 if (sBgWidgetProviders == null || refresh) {
3296 HashMap<ComponentKey, LauncherAppWidgetProviderInfo> tmpWidgetProviders
3297 = new HashMap<>();
3298 AppWidgetManagerCompat wm = AppWidgetManagerCompat.getInstance(context);
3299 LauncherAppWidgetProviderInfo info;
3300
3301 List<AppWidgetProviderInfo> widgets = wm.getAllProviders();
3302 for (AppWidgetProviderInfo pInfo : widgets) {
3303 info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
3304 UserHandleCompat user = wm.getUser(info);
3305 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3306 }
3307
3308 Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
3309 for (CustomAppWidget widget : customWidgets) {
3310 info = new LauncherAppWidgetProviderInfo(context, widget);
3311 UserHandleCompat user = wm.getUser(info);
3312 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
3313 }
3314 // Replace the global list at the very end, so that if there is an exception,
3315 // previously loaded provider list is used.
3316 sBgWidgetProviders = tmpWidgetProviders;
3317 }
3318 results.addAll(sBgWidgetProviders.values());
3319 return results;
3320 }
3321 } catch (Exception e) {
3322 if (e.getCause() instanceof TransactionTooLargeException) {
3323 // the returned value may be incomplete and will not be refreshed until the next
3324 // time Launcher starts.
3325 // TODO: after figuring out a repro step, introduce a dirty bit to check when
3326 // onResume is called to refresh the widget provider list.
3327 synchronized (sBgLock) {
3328 if (sBgWidgetProviders != null) {
3329 results.addAll(sBgWidgetProviders.values());
3330 }
3331 return results;
3332 }
3333 } else {
3334 throw e;
3335 }
3336 }
3337 }
3338
3339 public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name,
3340 UserHandleCompat user) {
3341 synchronized (sBgLock) {
3342 if (sBgWidgetProviders == null) {
3343 getWidgetProviders(ctx, false /* refresh */);
3344 }
3345 return sBgWidgetProviders.get(new ComponentKey(name, user));
3346 }
3347 }
3348
3349 public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks,
3350 final boolean refresh) {
3351
3352 runOnWorkerThread(new Runnable() {
3353 @Override
3354 public void run() {
3355 updateWidgetsModel(context, refresh);
3356 final WidgetsModel model = mBgWidgetsModel.clone();
3357
3358 mHandler.post(new Runnable() {
3359 @Override
3360 public void run() {
3361 Callbacks cb = getCallback();
3362 if (callbacks == cb && cb != null) {
3363 callbacks.bindAllPackages(model);
3364 }
3365 }
3366 });
3367 // update the Widget entries inside DB on the worker thread.
3368 LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
3369 model.getRawList());
3370 }
3371 });
3372 }
3373
3374 /**
3375 * Returns a list of ResolveInfos/AppWidgetInfos.
3376 *
3377 * @see #loadAndBindWidgetsAndShortcuts
3378 */
3379 @Thunk void updateWidgetsModel(Context context, boolean refresh) {
3380 PackageManager packageManager = context.getPackageManager();
3381 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3382 widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
3383 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3384 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3385 mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
3386 }
3387
3388 @Thunk static boolean isPackageDisabled(Context context, String packageName,
3389 UserHandleCompat user) {
3390 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3391 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3392 }
3393
3394 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3395 UserHandleCompat user) {
3396 if (cn == null) {
3397 return false;
3398 }
3399 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3400 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3401 return false;
3402 }
3403 return launcherApps.isActivityEnabledForProfile(cn, user);
3404 }
3405
3406 public static boolean isValidPackage(Context context, String packageName,
3407 UserHandleCompat user) {
3408 if (packageName == null) {
3409 return false;
3410 }
3411 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3412 return launcherApps.isPackageEnabledForProfile(packageName, user);
3413 }
3414
3415 /**
3416 * Make an ShortcutInfo object for a restored application or shortcut item that points
3417 * to a package that is not yet installed on the system.
3418 */
3419 public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent,
3420 int promiseType, int itemType, CursorIconInfo iconInfo, Context context) {
3421 final ShortcutInfo info = new ShortcutInfo();
3422 info.user = UserHandleCompat.myUserHandle();
3423
3424 Bitmap icon = iconInfo.loadIcon(c, info, context);
3425 // the fallback icon
3426 if (icon == null) {
3427 mIconCache.getTitleAndIcon(info, intent, info.user, false /* useLowResIcon */);
3428 } else {
3429 info.setIcon(icon);
3430 }
3431
3432 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3433 String title = (c != null) ? c.getString(titleIndex) : null;
3434 if (!TextUtils.isEmpty(title)) {
3435 info.title = Utilities.trim(title);
3436 }
3437 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3438 if (TextUtils.isEmpty(info.title)) {
3439 info.title = (c != null) ? Utilities.trim(c.getString(titleIndex)) : "";
3440 }
3441 } else {
3442 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3443 }
3444
3445 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3446 info.itemType = itemType;
3447 info.promisedIntent = intent;
3448 info.status = promiseType;
3449 return info;
3450 }
3451
3452 /**
3453 * Make an Intent object for a restored application or shortcut item that points
3454 * to the market page for the item.
3455 */
3456 @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3457 ComponentName componentName = intent.getComponent();
3458 return getMarketIntent(componentName.getPackageName());
3459 }
3460
3461 static Intent getMarketIntent(String packageName) {
3462 return new Intent(Intent.ACTION_VIEW)
3463 .setData(new Uri.Builder()
3464 .scheme("market")
3465 .authority("details")
3466 .appendQueryParameter("id", packageName)
3467 .build());
3468 }
3469
3470 /**
3471 * Make an ShortcutInfo object for a shortcut that is an application.
3472 *
3473 * If c is not null, then it will be used to fill in missing data like the title and icon.
3474 */
3475 public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent,
3476 UserHandleCompat user, Context context, Cursor c, int iconIndex, int titleIndex,
3477 boolean allowMissingTarget, boolean useLowResIcon) {
3478 if (user == null) {
3479 Log.d(TAG, "Null user found in getShortcutInfo");
3480 return null;
3481 }
3482
3483 ComponentName componentName = intent.getComponent();
3484 if (componentName == null) {
3485 Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
3486 return null;
3487 }
3488
3489 Intent newIntent = new Intent(intent.getAction(), null);
3490 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3491 newIntent.setComponent(componentName);
3492 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3493 if ((lai == null) && !allowMissingTarget) {
3494 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3495 return null;
3496 }
3497
3498 final ShortcutInfo info = new ShortcutInfo();
3499 mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
3500 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && c != null) {
3501 Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
3502 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3503 }
3504
3505 // from the db
3506 if (TextUtils.isEmpty(info.title) && c != null) {
3507 info.title = Utilities.trim(c.getString(titleIndex));
3508 }
3509
3510 // fall back to the class name of the activity
3511 if (info.title == null) {
3512 info.title = componentName.getClassName();
3513 }
3514
3515 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3516 info.user = user;
3517 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3518 if (lai != null) {
3519 info.flags = AppInfo.initFlags(lai);
3520 }
3521 return info;
3522 }
3523
3524 static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos,
3525 ItemInfoFilter f) {
3526 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3527 for (ItemInfo i : infos) {
3528 if (i instanceof ShortcutInfo) {
3529 ShortcutInfo info = (ShortcutInfo) i;
3530 ComponentName cn = info.getTargetComponent();
3531 if (cn != null && f.filterItem(null, info, cn)) {
3532 filtered.add(info);
3533 }
3534 } else if (i instanceof FolderInfo) {
3535 FolderInfo info = (FolderInfo) i;
3536 for (ShortcutInfo s : info.contents) {
3537 ComponentName cn = s.getTargetComponent();
3538 if (cn != null && f.filterItem(info, s, cn)) {
3539 filtered.add(s);
3540 }
3541 }
3542 } else if (i instanceof LauncherAppWidgetInfo) {
3543 LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
3544 ComponentName cn = info.providerName;
3545 if (cn != null && f.filterItem(null, info, cn)) {
3546 filtered.add(info);
3547 }
3548 }
3549 }
3550 return new ArrayList<ItemInfo>(filtered);
3551 }
3552
3553 @Thunk ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname,
3554 final UserHandleCompat user) {
3555 ItemInfoFilter filter = new ItemInfoFilter() {
3556 @Override
3557 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3558 if (info.user == null) {
3559 return cn.equals(cname);
3560 } else {
3561 return cn.equals(cname) && info.user.equals(user);
3562 }
3563 }
3564 };
3565 return filterItemInfos(sBgItemsIdMap, filter);
3566 }
3567
3568 /**
3569 * Make an ShortcutInfo object for a shortcut that isn't an application.
3570 */
3571 @Thunk ShortcutInfo getShortcutInfo(Cursor c, Context context,
3572 int titleIndex, CursorIconInfo iconInfo) {
3573 final ShortcutInfo info = new ShortcutInfo();
3574 // Non-app shortcuts are only supported for current user.
3575 info.user = UserHandleCompat.myUserHandle();
3576 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3577
3578 // TODO: If there's an explicit component and we can't install that, delete it.
3579
3580 info.title = Utilities.trim(c.getString(titleIndex));
3581
3582 Bitmap icon = iconInfo.loadIcon(c, info, context);
3583 // the fallback icon
3584 if (icon == null) {
3585 icon = mIconCache.getDefaultIcon(info.user);
3586 info.usingFallbackIcon = true;
3587 }
3588 info.setIcon(icon);
3589 return info;
3590 }
3591
3592 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3593 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3594 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3595 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3596
3597 if (intent == null) {
3598 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3599 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3600 return null;
3601 }
3602
3603 Bitmap icon = null;
3604 boolean customIcon = false;
3605 ShortcutIconResource iconResource = null;
3606
3607 if (bitmap instanceof Bitmap) {
3608 icon = Utilities.createIconBitmap((Bitmap) bitmap, context);
3609 customIcon = true;
3610 } else {
3611 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3612 if (extra instanceof ShortcutIconResource) {
3613 iconResource = (ShortcutIconResource) extra;
3614 icon = Utilities.createIconBitmap(iconResource.packageName,
3615 iconResource.resourceName, context);
3616 }
3617 }
3618
3619 final ShortcutInfo info = new ShortcutInfo();
3620
3621 // Only support intents for current user for now. Intents sent from other
3622 // users wouldn't get here without intent forwarding anyway.
3623 info.user = UserHandleCompat.myUserHandle();
3624 if (icon == null) {
3625 icon = mIconCache.getDefaultIcon(info.user);
3626 info.usingFallbackIcon = true;
3627 }
3628 info.setIcon(icon);
3629
3630 info.title = Utilities.trim(name);
3631 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3632 info.intent = intent;
3633 info.customIcon = customIcon;
3634 info.iconResource = iconResource;
3635
3636 return info;
3637 }
3638
3639 /**
3640 * Return an existing FolderInfo object if we have encountered this ID previously,
3641 * or make a new one.
3642 */
3643 @Thunk static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
3644 // See if a placeholder was created for us already
3645 FolderInfo folderInfo = folders.get(id);
3646 if (folderInfo == null) {
3647 // No placeholder -- create a new instance
3648 folderInfo = new FolderInfo();
3649 folders.put(id, folderInfo);
3650 }
3651 return folderInfo;
3652 }
3653
3654
3655 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3656 return (provider != null) && (provider.provider != null)
3657 && (provider.provider.getPackageName() != null);
3658 }
3659
3660 public void dumpState() {
3661 Log.d(TAG, "mCallbacks=" + mCallbacks);
3662 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3663 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3664 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3665 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3666 if (mLoaderTask != null) {
3667 mLoaderTask.dumpState();
3668 } else {
3669 Log.d(TAG, "mLoaderTask=null");
3670 }
3671 }
3672
3673 public Callbacks getCallback() {
3674 return mCallbacks != null ? mCallbacks.get() : null;
3675 }
3676
3677 /**
3678 * @return {@link FolderInfo} if its already loaded.
3679 */
3680 public FolderInfo findFolderById(Long folderId) {
3681 synchronized (sBgLock) {
3682 return sBgFolders.get(folderId);
3683 }
3684 }
3685
3686 /**
3687 * @return the looper for the worker thread which can be used to start background tasks.
3688 */
3689 public static Looper getWorkerLooper() {
3690 return sWorkerThread.getLooper();
3691 }
3692 }
|
1 /*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.android.launcher3;
17
18 import android.app.SearchManager;
19 import android.appwidget.AppWidgetProviderInfo;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.ContentProviderOperation;
23 import android.content.ContentResolver;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.Intent.ShortcutIconResource;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ProviderInfo;
31 import android.content.pm.ResolveInfo;
32 import android.database.Cursor;
33 import android.graphics.Bitmap;
34 import android.net.Uri;
35 import android.os.Build;
36 import android.os.Environment;
37 import android.os.Handler;
38 import android.os.HandlerThread;
39 import android.os.Looper;
40 import android.os.Parcelable;
41 import android.os.Process;
42 import android.os.SystemClock;
43 import android.os.TransactionTooLargeException;
44 import android.provider.BaseColumns;
45 import android.text.TextUtils;
46 import android.util.Log;
47 import android.util.LongSparseArray;
48 import android.util.Pair;
49 import com.android.launcher3.compat.AppWidgetManagerCompat;
50 import com.android.launcher3.compat.LauncherActivityInfoCompat;
51 import com.android.launcher3.compat.LauncherAppsCompat;
52 import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
53 import com.android.launcher3.compat.PackageInstallerCompat;
54 import com.android.launcher3.compat.UserHandleCompat;
55 import com.android.launcher3.compat.UserManagerCompat;
56 import com.android.launcher3.model.WidgetsModel;
57 import com.android.launcher3.util.ComponentKey;
58 import com.android.launcher3.util.CursorIconInfo;
59 import com.android.launcher3.util.LongArrayMap;
60 import com.android.launcher3.util.ManagedProfileHeuristic;
61 import com.android.launcher3.util.Thunk;
62 import java.lang.ref.WeakReference;
63 import java.net.URISyntaxException;
64 import java.security.InvalidParameterException;
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.Collection;
68 import java.util.Collections;
69 import java.util.Comparator;
70 import java.util.HashMap;
71 import java.util.HashSet;
72 import java.util.Iterator;
73 import java.util.List;
74 import java.util.Map.Entry;
75 import java.util.Set;
76
77
78 /**
79 * Maintains in-memory state of the Launcher. It is expected that there should be only one
80 * LauncherModel object held in a static. Also provide APIs for updating the database state
81 * for the Launcher.
82 */
83 public class LauncherModel extends BroadcastReceiver implements LauncherAppsCompat.OnAppsChangedCallbackC🔵
84 static final boolean DEBUG_LOADERS = false;
85
86 private static final boolean DEBUG_RECEIVER = false;
87
88 private static final boolean REMOVE_UNRESTORED_ICONS = true;
89
90 static final String TAG = "Launcher.Model";
91
92 public static final int LOADER_FLAG_NONE = 0;
93
94 public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0;
95
96 public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1;
97
98 // batch size for the workspace icons
99 private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
100
101 private static final long INVALID_SCREEN_ID = -1L;
102
103 @Thunk
104 final boolean mAppsCanBeOnRemoveableStorage;
105
106 private final boolean mOldContentProviderExists;
107
108 @Thunk
109 final LauncherAppState mApp;
110
111 @Thunk
112 final Object mLock = new Object();
113
114 @Thunk
115 DeferredHandler mHandler = new DeferredHandler();
116
117 @Thunk
118 LoaderTask mLoaderTask;
119
120 @Thunk
121 boolean mIsLoaderTaskRunning;
122
123 @Thunk
124 boolean mHasLoaderCompletedOnce;
125
126 private static final String MIGRATE_AUTHORITY = "com.android.launcher2.settings";
127
128 @Thunk
129 static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
130
131 static {
132 sWorkerThread.start();
133 }
134
135 @Thunk
136 static final Handler sWorker = new Handler(sWorkerThread.getLooper());
137
138 // We start off with everything not loaded. After that, we assume that
139 // our monitoring of the package manager provides all updates and we never
140 // need to do a requery. These are only ever touched from the loader thread.
141 @Thunk
142 boolean mWorkspaceLoaded;
143
144 @Thunk
145 boolean mAllAppsLoaded;
146
147 // When we are loading pages synchronously, we can't just post the binding of items on the side
148 // pages as this delays the rotation process. Instead, we wait for a callback from the first
149 // draw (in Workspace) to initiate the binding of the remaining side pages. Any time we start
150 // a normal load, we also clear this set of Runnables.
151 static final ArrayList<Runnable> mDeferredBindRunnables = new ArrayList<Runnable>();
152
153 /**
154 * Set of runnables to be called on the background thread after the workspace binding
155 * is complete.
156 */
157 static final ArrayList<Runnable> mBindCompleteRunnables = new ArrayList<Runnable>();
158
159 @Thunk
160 WeakReference<Callbacks> mCallbacks;
161
162 // < only access in worker thread >
163 // < only access in worker thread >
164 AllAppsList mBgAllAppsList;
165
166 // Entire list of widgets.
167 // Entire list of widgets.
168 WidgetsModel mBgWidgetsModel;
169
170 // The lock that must be acquired before referencing any static bg data structures. Unlike
171 // other locks, this one can generally be held long-term because we never expect any of these
172 // static data structures to be referenced outside of the worker thread except on the first
173 // load after configuration change.
174 static final Object sBgLock = new Object();
175
176 // sBgItemsIdMap maps *all* the ItemInfos (shortcuts, folders, and widgets) created by
177 // LauncherModel to their ids
178 static final LongArrayMap<ItemInfo> sBgItemsIdMap = new LongArrayMap<>();
179
180 // sBgWorkspaceItems is passed to bindItems, which expects a list of all folders and shortcuts
181 // created by LauncherModel that are directly on the home screen (however, no widgets or
182 // shortcuts within folders).
183 static final ArrayList<ItemInfo> sBgWorkspaceItems = new ArrayList<ItemInfo>();
184
185 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
186 // sBgAppWidgets is all LauncherAppWidgetInfo created by LauncherModel. Passed to bindAppWidget()
187 static final ArrayList<LauncherAppWidgetInfo> sBgAppWidgets =
188 new ArrayList<LauncherAppWidgetInfo>();
189
190 // sBgFolders is all FolderInfos created by LauncherModel. Passed to bindFolders()
191 static final LongArrayMap<FolderInfo> sBgFolders = new LongArrayMap<>();
192
193 // sBgWorkspaceScreens is the ordered set of workspace screens.
194 // sBgWorkspaceScreens is the ordered set of workspace screens.
195 static final ArrayList<Long> sBgWorkspaceScreens = new ArrayList<Long>();
196
197 // sBgWidgetProviders is the set of widget providers including custom internal widgets
198 public static HashMap<ComponentKey, LauncherAppWidgetProviderInfo> sBgWidgetProviders;
199
200 // sPendingPackages is a set of packages which could be on sdcard and are not available yet
201 static final HashMap<UserHandleCompat, HashSet<String>> sPendingPackages = new HashMap<UserHandleComp🔵
202
203 // </ only access in worker thread >
204 @Thunk
205 IconCache mIconCache;
206
207 @Thunk
208 final LauncherAppsCompat mLauncherApps;
209
210 @Thunk
211 final UserManagerCompat mUserManager;
212
213 public interface Callbacks {
214 public boolean setLoadOnResume();
215
216 public int getCurrentWorkspaceScreen();
217
218 public void startBinding();
219
220 public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
221 boolean forceAnimateIcons);
222
223 public void bindScreens(ArrayList<Long> orderedScreenIds);
224
225 public void bindAddScreens(ArrayList<Long> orderedScreenIds);
226
227 public abstract void bindFolders(LongArrayMap<FolderInfo> folders);
228
229 public abstract void finishBindingItems();
230
231 public void bindAppWidget(LauncherAppWidgetInfo info);
232
233 public void bindAllApplications(ArrayList<AppInfo> apps);
234
235 public void bindAppsAdded(ArrayList<Long> newScreens,
236 ArrayList<ItemInfo> addNotAnimated,
237 ArrayList<ItemInfo> addAnimated,
238 ArrayList<AppInfo> addedApps);
239
240 public void bindAppsUpdated(ArrayList<AppInfo> apps);
241
242 public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
243 ArrayList<ShortcutInfo> removed, UserHandleCompat user);
244
245 public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
246
247 public abstract void bindRestoreItemsChange(HashSet<ItemInfo> updates);
248
249 public void bindComponentsRemoved(ArrayList<String> packageNames,
250 ArrayList<AppInfo> appInfos, UserHandleCompat user, int reason);
251
252 public abstract void bindAllPackages(WidgetsModel model);
253
254 public void bindSearchablesChanged();
255
256 public boolean isAllAppsButtonRank(int rank);
257
258 public void onPageBoundSynchronously(int page);
259
260 public void dumpLogsToLocalData();
261 }
262
263 public interface ItemInfoFilter {
264 public abstract boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn);
265 }
266
267 LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
268 Context context = app.getContext();
269 mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();
270 String oldProvider = context.getString(R.string.old_launcher_provider_uri);
271 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
272 // resource string.
273 String redirectAuthority = Uri.parse(oldProvider).getAuthority();
274 ProviderInfo providerInfo = context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY,🔵
275 ProviderInfo redirectProvider = context.getPackageManager().resolveContentProvider(redirectAuthor🔵
276 Log.d(TAG, "Old launcher provider: " + oldProvider);
277 mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);
278 if (mOldContentProviderExists) {
279 Log.d(TAG, "Old launcher provider exists.");
280 } else {
281 Log.d(TAG, "Old launcher provider does not exist.");
282 }
283 mApp = app;
284 mBgAllAppsList = new AllAppsList(iconCache, appFilter);
285 mBgWidgetsModel = new WidgetsModel(context, iconCache, appFilter);
286 mIconCache = iconCache;
287 mLauncherApps = LauncherAppsCompat.getInstance(context);
288 mUserManager = UserManagerCompat.getInstance(context);
289 }
290
291 /** Runs the specified runnable immediately if called from the main thread, otherwise it is
292 * posted on the main thread handler. */
293 @Thunk
294 void runOnMainThread(Runnable r) {
295 if (sWorkerThread.getThreadId() == Process.myTid()) {
296 // If we are on the worker thread, post onto the main handler
297 mHandler.post(r);
298 } else {
299 r.run();
300 }
301 }
302
303 /** Runs the specified runnable immediately if called from the worker thread, otherwise it is
304 * posted on the worker thread handler. */
305 private static void runOnWorkerThread(Runnable r) {
306 if (sWorkerThread.getThreadId() == Process.myTid()) {
307 r.run();
308 } else {
309 // If we are not on the worker thread, then post to the worker handler
310 sWorker.post(r);
311 }
312 }
313
314 /**
315 * Runs the specified runnable after the loader is complete
316 */
317 @Thunk
318 void runAfterBindCompletes(Runnable r) {
319 if (isLoadingWorkspace() || (!mHasLoaderCompletedOnce)) {
320 synchronized(mBindCompleteRunnables) {
321 mBindCompleteRunnables.add(r);
322 }
323 } else {
324 runOnWorkerThread(r);
325 }
326 }
327
328 boolean canMigrateFromOldLauncherDb(Launcher launcher) {
329 return mOldContentProviderExists && !launcher.isLauncherPreinstalled() ;
330 }
331
332 public void setPackageState(final PackageInstallInfo installInfo) {
333 Runnable updateRunnable = new Runnable() {
334 @Override
335 public void run() {
336 synchronized(sBgLock) {
337 final HashSet<ItemInfo> updates = new HashSet<>();
338 if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
339 // Ignore install success events as they are handled by Package add events.
340 return;
341 }
342 for (ItemInfo info : sBgItemsIdMap) {
343 if (info instanceof ShortcutInfo) {
344 ShortcutInfo si = ((ShortcutInfo) (info));
345 ComponentName cn = si.getTargetComponent();
346 if ((si.isPromise() && (cn != null)) && installInfo.packageName.equals(cn.get🔵
347 si.setInstallProgress(installInfo.progress);
348 if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) {
349 // Mark this info as broken.
350 si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
351 }
352 updates.add(si);
353 }
354 }
355 }
356 for (LauncherAppWidgetInfo widget : sBgAppWidgets) {
357 if (widget.providerName.getPackageName().equals(installInfo.packageName)) {
358 widget.installProgress = installInfo.progress;
359 updates.add(widget);
360 }
361 }
362 if (!updates.isEmpty()) {
363 // Push changes to the callback.
364 Runnable r = new Runnable() {
365 public void run() {
366 Callbacks callbacks = getCallback();
367 if (callbacks != null) {
368 callbacks.bindRestoreItemsChange(updates);
369 }
370 }
371 };
372 mHandler.post(r);
373 }
374 }
375 }
376 };
377 runOnWorkerThread(updateRunnable);
378 }
379
380 /**
381 * Updates the icons and label of all pending icons for the provided package name.
382 */
383 public void updateSessionDisplayInfo(final String packageName) {
384 Runnable updateRunnable = new Runnable() {
385 @Override
386 public void run() {
387 synchronized(sBgLock) {
388 final ArrayList<ShortcutInfo> updates = new ArrayList<>();
389 final UserHandleCompat user = UserHandleCompat.myUserHandle();
390 for (ItemInfo info : sBgItemsIdMap) {
391 if (info instanceof ShortcutInfo) {
392 ShortcutInfo si = ((ShortcutInfo) (info));
393 ComponentName cn = si.getTargetComponent();
394 if ((si.isPromise() && (cn != null)) && packageName.equals(cn.getPackageName(🔵
395 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
396 // For auto install apps update the icon as well as label.
397 mIconCache.getTitleAndIcon(si, si.promisedIntent, user, si.shouldUseL🔵
398 } else {
399 // Only update the icon for restored apps.
400 si.updateIcon(mIconCache);
401 }
402 updates.add(si);
403 }
404 }
405 }
406 if (!updates.isEmpty()) {
407 // Push changes to the callback.
408 Runnable r = new Runnable() {
409 public void run() {
410 Callbacks callbacks = getCallback();
411 if (callbacks != null) {
412 callbacks.bindShortcutsChanged(updates, new ArrayList<ShortcutInfo>()🔵
413 }
414 }
415 };
416 mHandler.post(r);
417 }
418 }
419 }
420 };
421 runOnWorkerThread(updateRunnable);
422 }
423
424 public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) {
425 final Callbacks callbacks = getCallback();
426 if (allAppsApps == null) {
427 throw new RuntimeException("allAppsApps must not be null");
428 }
429 if (allAppsApps.isEmpty()) {
430 return;
431 }
432 // Process the newly added applications and add them to the database first
433 Runnable r = new Runnable() {
434 public void run() {
435 runOnMainThread(new Runnable() {
436 public void run() {
437 Callbacks cb = getCallback();
438 if ((callbacks == cb) && (cb != null)) {
439 callbacks.bindAppsAdded(null, null, null, allAppsApps);
440 }
441 }
442 });
443 }
444 };
445 runOnWorkerThread(r);
446 }
447
448 private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos, int[] xy, 🔵
449 LauncherAppState app = LauncherAppState.getInstance();
450 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
451 final int xCount = ((int) (profile.numColumns));
452 final int yCount = ((int) (profile.numRows));
453 boolean[][] occupied = new boolean[xCount][yCount];
454 if (occupiedPos != null) {
455 for (ItemInfo r : occupiedPos) {
456 int right = r.cellX + r.spanX;
457 int bottom = r.cellY + r.spanY;
458 for (int x = r.cellX; ((0 <= x) && (x < right)) && (x < xCount); x++) {
459 for (int y = r.cellY; ((0 <= y) && (y < bottom)) && (y < yCount); y++) {
460 occupied[x][y] = true;
461 }
462 }
463 }
464 }
465 return Utilities.findVacantCell(xy, spanX, spanY, xCount, yCount, occupied);
466 }
467
468 /**
469 * Find a position on the screen for the given size or adds a new screen.
470 * @return screenId and the coordinates for the item.
471 */
472 @Thunk
473 Pair<Long, int[]> findSpaceForItem(Context context, ArrayList<Long> workspaceScreens, ArrayList<Long>🔵
474 LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
475 // Use sBgItemsIdMap as all the items are already loaded.
476 assertWorkspaceLoaded();
477 synchronized(sBgLock) {
478 for (ItemInfo info : sBgItemsIdMap) {
479 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
480 ArrayList<ItemInfo> items = screenItems.get(info.screenId);
481 if (items == null) {
482 items = new ArrayList<>();
483 screenItems.put(info.screenId, items);
484 }
485 items.add(info);
486 }
487 }
488 }
489 // Find appropriate space for the item.
490 long screenId = 0;
491 int[] cordinates = new int[2];
492 boolean found = false;
493 int screenCount = workspaceScreens.size();
494 // First check the preferred screen.
495 int preferredScreenIndex = (workspaceScreens.isEmpty()) ? 0 : 1;
496 if (preferredScreenIndex < screenCount) {
497 screenId = workspaceScreens.get(preferredScreenIndex);
498 found = findNextAvailableIconSpaceInScreen(screenItems.get(screenId), cordinates, spanX, span🔵
499 }
500 if (!found) {
501 // Search on any of the screens starting from the first screen.
502 for (int screen = 1; screen < screenCount; screen++) {
503 screenId = workspaceScreens.get(screen);
504 if (findNextAvailableIconSpaceInScreen(screenItems.get(screenId), cordinates, spanX, span🔵
505 // We found a space for it
506 found = true;
507 break;
508 }
509 }
510 }
511 if (!found) {
512 // Still no position found. Add a new screen to the end.
513 screenId = LauncherAppState.getLauncherProvider().generateNewScreenId();
514 // Save the screen id for binding in the workspace
515 workspaceScreens.add(screenId);
516 addedWorkspaceScreensFinal.add(screenId);
517 // If we still can't find an empty space, then God help us all!!!
518 if (!findNextAvailableIconSpaceInScreen(screenItems.get(screenId), cordinates, spanX, spanY))🔵
519 throw new RuntimeException("Can't find space to add the item");
520 }
521 }
522 return Pair.create(screenId, cordinates);
523 }
524
525 /**
526 * Adds the provided items to the workspace.
527 */
528 public void addAndBindAddedWorkspaceItems(final Context context, final ArrayList<? extends ItemInfo> 🔵
529 final Callbacks callbacks = getCallback();
530 if (workspaceApps.isEmpty()) {
531 return;
532 }
533 // Process the newly added applications and add them to the database first
534 Runnable r = new Runnable() {
535 public void run() {
536 final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
537 final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
538 // Get the list of workspace screens. We need to append to this list and
539 // can not use sBgWorkspaceScreens because loadWorkspace() may not have been
540 // called.
541 ArrayList<Long> workspaceScreens = loadWorkspaceScreensDb(context);
542 synchronized(sBgLock) {
543 for (ItemInfo item : workspaceApps) {
544 if (item instanceof ShortcutInfo) {
545 // Short-circuit this logic if the icon exists somewhere on the workspace
546 if (shortcutExists(context, item.getIntent(), item.user)) {
547 continue;
548 }
549 }
550 // Find appropriate space for the item.
551 Pair<Long, int[]> coords = findSpaceForItem(context, workspaceScreens, addedWorks🔵
552 long screenId = coords.first;
553 int[] cordinates = coords.second;
554 ItemInfo itemInfo;
555 if ((item instanceof ShortcutInfo) || (item instanceof FolderInfo)) {
556 itemInfo = item;
557 } else if (item instanceof AppInfo) {
558 itemInfo = ((AppInfo) (item)).makeShortcut();
559 } else {
560 throw new RuntimeException("Unexpected info type");
561 }
562 // Add the shortcut to the db
563 addItemToDatabase(context, itemInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP🔵
564 // Save the ShortcutInfo for binding in the workspace
565 addedShortcutsFinal.add(itemInfo);
566 }
567 }
568 // Update the workspace screens
569 updateWorkspaceScreenOrder(context, workspaceScreens);
570 if (!addedShortcutsFinal.isEmpty()) {
571 runOnMainThread(new Runnable() {
572 public void run() {
573 Callbacks cb = getCallback();
574 if ((callbacks == cb) && (cb != null)) {
575 final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
576 final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
577 if (!addedShortcutsFinal.isEmpty()) {
578 ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 🔵
579 long lastScreenId = info.screenId;
580 for (ItemInfo i : addedShortcutsFinal) {
581 if (i.screenId == lastScreenId) {
582 addAnimated.add(i);
583 } else {
584 addNotAnimated.add(i);
585 }
586 }
587 }
588 callbacks.bindAppsAdded(addedWorkspaceScreensFinal, addNotAnimated, addAn🔵
589 }
590 }
591 });
592 }
593 }
594 };
595 runOnWorkerThread(r);
596 }
597
598 private void unbindItemInfosAndClearQueuedBindRunnables() {
599 if (sWorkerThread.getThreadId() == Process.myTid()) {
600 throw new RuntimeException("Expected unbindLauncherItemInfos() to be called from the " + "mai🔵
601 }
602 // Clear any deferred bind runnables
603 synchronized(mDeferredBindRunnables) {
604 mDeferredBindRunnables.clear();
605 }
606 // Remove any queued UI runnables
607 mHandler.cancelAll();
608 // Unbind all the workspace items
609 unbindWorkspaceItemsOnMainThread();
610 }
611
612 /** Unbinds all the sBgWorkspaceItems and sBgAppWidgets on the main thread */
613 void unbindWorkspaceItemsOnMainThread() {
614 // Ensure that we don't use the same workspace items data structure on the main thread
615 // by making a copy of workspace items first.
616 final ArrayList<ItemInfo> tmpItems = new ArrayList<ItemInfo>();
617 synchronized(sBgLock) {
618 tmpItems.addAll(sBgWorkspaceItems);
619 tmpItems.addAll(sBgAppWidgets);
620 }
621 Runnable r = new Runnable() {
622 @Override
623 public void run() {
624 for (ItemInfo item : tmpItems) {
625 item.unbind();
626 }
627 }
628 };
629 runOnMainThread(r);
630 }
631
632 /**
633 * Adds an item to the DB if it was not created previously, or move it to a new
634 * <container, screen, cellX, cellY>
635 */
636 static void addOrMoveItemInDatabase(Context context, ItemInfo item, long container, long screenId, in🔵
637 if (item.container == ItemInfo.NO_ID) {
638 // From all apps
639 addItemToDatabase(context, item, container, screenId, cellX, cellY);
640 } else {
641 // From somewhere else
642 moveItemInDatabase(context, item, container, screenId, cellX, cellY);
643 }
644 }
645
646 static void checkItemInfoLocked(final long itemId, final ItemInfo item, StackTraceElement[] stackTrac🔵
647 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
648 if ((modelItem != null) && (item != modelItem)) {
649 // check all the data is consistent
650 if ((modelItem instanceof ShortcutInfo) && (item instanceof ShortcutInfo)) {
651 ShortcutInfo modelShortcut = ((ShortcutInfo) (modelItem));
652 ShortcutInfo shortcut = ((ShortcutInfo) (item));
653 if ((((((((((modelShortcut.title.toString().equals(shortcut.title.toString()) && modelSho🔵
654 // For all intents and purposes, this is the same object
655 return;
656 }
657 }
658 // the modelItem needs to match up perfectly with item if our model is
659 // to be consistent with the database-- for now, just require
660 // modelItem == item or the equality check above
661 String msg = ((("item: " + (item != null ? item.toString() : "null")) + "modelItem: ") + (mod🔵
662 RuntimeException e = new RuntimeException(msg);
663 if (stackTrace != null) {
664 e.setStackTrace(stackTrace);
665 }
666 throw e;
667 }
668 }
669
670 static void checkItemInfo(final ItemInfo item) {
671 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
672 final long itemId = item.id;
673 Runnable r = new Runnable() {
674 public void run() {
675 synchronized (sBgLock) {
676 checkItemInfoLocked(itemId, item, stackTrace);
677 }
678 }
679 };
680 runOnWorkerThread(r);
681 }
682
683 static void updateItemInDatabaseHelper(Context context, final ContentValues values, final ItemInfo it🔵
684 final long itemId = item.id;
685 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
686 final ContentResolver cr = context.getContentResolver();
687 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
688 Runnable r = new Runnable() {
689 public void run() {
690 cr.update(uri, values, null, null);
691 updateItemArrays(item, itemId, stackTrace);
692 }
693 };
694 runOnWorkerThread(r);
695 }
696
697 static void updateItemsInDatabaseHelper(Context context, final ArrayList<ContentValues> valuesList, f🔵
698 final ContentResolver cr = context.getContentResolver();
699 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
700 Runnable r = new Runnable() {
701 public void run() {
702 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
703 int count = items.size();
704 for (int i = 0; i < count; i++) {
705 ItemInfo item = items.get(i);
706 final long itemId = item.id;
707 final Uri uri = LauncherSettings.Favorites.getContentUri(itemId);
708 ContentValues values = valuesList.get(i);
709 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
710 updateItemArrays(item, itemId, stackTrace);
711 }
712 try {
713 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
714 } catch (java.lang.Exception e) {
715 e.printStackTrace();
716 }
717 }
718 };
719 runOnWorkerThread(r);
720 }
721
722 static void updateItemArrays(ItemInfo item, long itemId, StackTraceElement[] stackTrace) {
723 // Lock on mBgLock *after* the db operation
724 synchronized(sBgLock) {
725 checkItemInfoLocked(itemId, item, stackTrace);
726 if ((item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) && (item.container != La🔵
727 // Item is in a folder, make sure this folder exists
728 if (!sBgFolders.containsKey(item.container)) {
729 // An items container is being set to a that of an item which is not in
730 // the list of Folders.
731 String msg = ((("item: " + item) + " container being set to: ") + item.container) + "🔵
732 Log.e(TAG, msg);
733 }
734 }
735 // Items are added/removed from the corresponding FolderInfo elsewhere, such
736 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
737 // that are on the desktop, as appropriate
738 ItemInfo modelItem = sBgItemsIdMap.get(itemId);
739 if ((modelItem != null) && ((modelItem.container == LauncherSettings.Favorites.CONTAINER_DESK🔵
740 switch (modelItem.itemType) {
741 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
742 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
743 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
744 if (!sBgWorkspaceItems.contains(modelItem)) {
745 sBgWorkspaceItems.add(modelItem);
746 }
747 break;
748 default :
749 break;
750 }
751 } else {
752 sBgWorkspaceItems.remove(modelItem);
753 }
754 }
755 }
756
757 /**
758 * Move an item in the DB to a new <container, screen, cellX, cellY>
759 */
760 public static void moveItemInDatabase(Context context, final ItemInfo item, final long container, fin🔵
761 item.container = container;
762 item.cellX = cellX;
763 item.cellY = cellY;
764 // We store hotseat items in canonical form which is this orientation invariant position
765 // in the hotseat
766 if (((context instanceof Launcher) && (screenId < 0)) && (container == LauncherSettings.Favorites🔵
767 item.screenId = ((Launcher) (context)).getHotseat().getOrderInHotseat(cellX, cellY);
768 } else {
769 item.screenId = screenId;
770 }
771 final ContentValues values = new ContentValues();
772 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
773 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
774 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
775 values.put(LauncherSettings.Favorites.RANK, item.rank);
776 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
777 updateItemInDatabaseHelper(context, values, item, "moveItemInDatabase");
778 }
779
780 /**
781 * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
782 * cellX, cellY have already been updated on the ItemInfos.
783 */
784 static void moveItemsInDatabase(Context context, final ArrayList<ItemInfo> items,
785 final long container, final int screen) {
786
787 ArrayList<ContentValues> contentValues = new ArrayList<ContentValues>();
788 int count = items.size();
789
790 for (int i = 0; i < count; i++) {
791 ItemInfo item = items.get(i);
792 item.container = container;
793
794 // We store hotseat items in canonical form which is this orientation invariant position
795 // in the hotseat
796 if (context instanceof Launcher && screen < 0 &&
797 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
798 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(item.cellX,
799 item.cellY);
800 } else {
801 item.screenId = screen;
802 }
803
804 final ContentValues values = new ContentValues();
805 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
806 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
807 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
808 values.put(LauncherSettings.Favorites.RANK, item.rank);
809 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
810
811 contentValues.add(values);
812 }
813 updateItemsInDatabaseHelper(context, contentValues, items, "moveItemInDatabase");
814 }
815
816 /**
817 * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
818 */
819 static void modifyItemInDatabase(Context context, final ItemInfo item, final long container,
820 final long screenId, final int cellX, final int cellY, final int spanX, final int spanY) {
821 item.container = container;
822 item.cellX = cellX;
823 item.cellY = cellY;
824 item.spanX = spanX;
825 item.spanY = spanY;
826
827 // We store hotseat items in canonical form which is this orientation invariant position
828 // in the hotseat
829 if (context instanceof Launcher && screenId < 0 &&
830 container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
831 item.screenId = ((Launcher) context).getHotseat().getOrderInHotseat(cellX, cellY);
832 } else {
833 item.screenId = screenId;
834 }
835
836 final ContentValues values = new ContentValues();
837 values.put(LauncherSettings.Favorites.CONTAINER, item.container);
838 values.put(LauncherSettings.Favorites.CELLX, item.cellX);
839 values.put(LauncherSettings.Favorites.CELLY, item.cellY);
840 values.put(LauncherSettings.Favorites.RANK, item.rank);
841 values.put(LauncherSettings.Favorites.SPANX, item.spanX);
842 values.put(LauncherSettings.Favorites.SPANY, item.spanY);
843 values.put(LauncherSettings.Favorites.SCREEN, item.screenId);
844
845 updateItemInDatabaseHelper(context, values, item, "modifyItemInDatabase");
846 }
847
848 /**
849 * Update an item to the database in a specified container.
850 */
851 public static void updateItemInDatabase(Context context, final ItemInfo item) {
852 final ContentValues values = new ContentValues();
853 item.onAddToDatabase(context, values);
854 updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase");
855 }
856
857 private void assertWorkspaceLoaded() {
858 if (LauncherAppState.isDogfoodBuild() && (isLoadingWorkspace() || !mHasLoaderCompletedOnce)) {
859 throw new RuntimeException("Trying to add shortcut while loader is running");
860 }
861 }
862
863 /**
864 * Returns true if the shortcuts already exists on the workspace. This must be called after
865 * the workspace has been loaded. We identify a shortcut by its intent.
866 */
867 @Thunk
868 boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) {
869 assertWorkspaceLoaded();
870 final String intentWithPkg;
871 final String intentWithoutPkg;
872 if (intent.getComponent() != null) {
873 // If component is not null, an intent with null package will produce
874 // the same result and should also be a match.
875 String packageName = intent.getComponent().getPackageName();
876 if (intent.getPackage() != null) {
877 intentWithPkg = intent.toUri(0);
878 intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
879 } else {
880 intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
881 intentWithoutPkg = intent.toUri(0);
882 }
883 } else {
884 intentWithPkg = intent.toUri(0);
885 intentWithoutPkg = intent.toUri(0);
886 }
887 synchronized(sBgLock) {
888 for (ItemInfo item : sBgItemsIdMap) {
889 if (item instanceof ShortcutInfo) {
890 ShortcutInfo info = ((ShortcutInfo) (item));
891 Intent targetIntent = (info.promisedIntent == null) ? info.intent : info.promisedInte🔵
892 if ((targetIntent != null) && info.user.equals(user)) {
893 String s = targetIntent.toUri(0);
894 if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
895 return true;
896 }
897 }
898 }
899 }
900 }
901 return false;
902 }
903
904 /**
905 * Find a folder in the db, creating the FolderInfo if necessary, and adding it to folderList.
906 */
907 FolderInfo getFolderById(Context context, LongArrayMap<FolderInfo> folderList, long id) {
908 final ContentResolver cr = context.getContentResolver();
909 Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, null, "_id=? and (itemType=? or itemT🔵
910 try {
911 if (c.moveToFirst()) {
912 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
913 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
914 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER);
915 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
916 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
917 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
918 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
919 FolderInfo folderInfo = null;
920 switch (c.getInt(itemTypeIndex)) {
921 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
922 folderInfo = findOrMakeFolder(folderList, id);
923 break;
924 }
925 // Do not trim the folder label, as is was set by the user.
926 folderInfo.title = c.getString(titleIndex);
927 folderInfo.id = id;
928 folderInfo.container = c.getInt(containerIndex);
929 folderInfo.screenId = c.getInt(screenIndex);
930 folderInfo.cellX = c.getInt(cellXIndex);
931 folderInfo.cellY = c.getInt(cellYIndex);
932 folderInfo.options = c.getInt(optionsIndex);
933 return folderInfo;
934 }
935 } finally {
936 c.close();
937 }
938 return null;
939 }
940
941 /**
942 * Add an item to the database in a specified container. Sets the container, screen, cellX and
943 * cellY fields of the item. Also assigns an ID to the item.
944 */
945 public static void addItemToDatabase(Context context, final ItemInfo item, final long container, fina🔵
946 item.container = container;
947 item.cellX = cellX;
948 item.cellY = cellY;
949 // We store hotseat items in canonical form which is this orientation invariant position
950 // in the hotseat
951 if (((context instanceof Launcher) && (screenId < 0)) && (container == LauncherSettings.Favorites🔵
952 item.screenId = ((Launcher) (context)).getHotseat().getOrderInHotseat(cellX, cellY);
953 } else {
954 item.screenId = screenId;
955 }
956 final ContentValues values = new ContentValues();
957 final ContentResolver cr = context.getContentResolver();
958 item.onAddToDatabase(context, values);
959 item.id = LauncherAppState.getLauncherProvider().generateNewItemId();
960 values.put(LauncherSettings.Favorites._ID, item.id);
961 final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
962 Runnable r = new Runnable() {
963 public void run() {
964 cr.insert(LauncherSettings.Favorites.CONTENT_URI, values);
965 // Lock on mBgLock *after* the db operation
966 synchronized(sBgLock) {
967 checkItemInfoLocked(item.id, item, stackTrace);
968 sBgItemsIdMap.put(item.id, item);
969 switch (item.itemType) {
970 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
971 sBgFolders.put(item.id, ((FolderInfo) (item)));
972 // Fall through
973 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
974 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
975 if ((item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) || (item🔵
976 sBgWorkspaceItems.add(item);
977 } else if (!sBgFolders.containsKey(item.container)) {
978 // Adding an item to a folder that doesn't exist.
979 String msg = (("adding item: " + item) + " to a folder that ") + " doesn'🔵
980 Log.e(TAG, msg);
981 }
982 break;
983 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
984 sBgAppWidgets.add(((LauncherAppWidgetInfo) (item)));
985 break;
986 }
987 }
988 }
989 };
990 runOnWorkerThread(r);
991 }
992
993 /**
994 * Creates a new unique child id, for a given cell span across all layouts.
995 */
996 static int getCellLayoutChildId(
997 long container, long screen, int localCellX, int localCellY, int spanX, int spanY) {
998 return (((int) container & 0xFF) << 24)
999 | ((int) screen & 0xFF) << 16 | (localCellX & 0xFF) << 8 | (localCellY & 0xFF);
1000 }
1001
1002 private static ArrayList<ItemInfo> getItemsByPackageName(final String pn, final UserHandleCompat user🔵
1003 ItemInfoFilter filter = new ItemInfoFilter() {
1004 @Override
1005 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
1006 return cn.getPackageName().equals(pn) && info.user.equals(user);
1007 }
1008 };
1009 return filterItemInfos(sBgItemsIdMap, filter);
1010 }
1011
1012 /**
1013 * Removes all the items from the database corresponding to the specified package.
1014 */
1015 static void deletePackageFromDatabase(Context context, final String pn,
1016 final UserHandleCompat user) {
1017 deleteItemsFromDatabase(context, getItemsByPackageName(pn, user));
1018 }
1019
1020 /**
1021 * Removes the specified item from the database
1022 * @param context
1023 * @param item
1024 */
1025 public static void deleteItemFromDatabase(Context context, final ItemInfo item) {
1026 ArrayList<ItemInfo> items = new ArrayList<ItemInfo>();
1027 items.add(item);
1028 deleteItemsFromDatabase(context, items);
1029 }
1030
1031 /**
1032 * Removes the specified items from the database
1033 * @param context
1034 * @param item
1035 */
1036 static void deleteItemsFromDatabase(Context context, final ArrayList<? extends ItemInfo> items) {
1037 final ContentResolver cr = context.getContentResolver();
1038 Runnable r = new Runnable() {
1039 public void run() {
1040 for (ItemInfo item : items) {
1041 final Uri uri = LauncherSettings.Favorites.getContentUri(item.id);
1042 cr.delete(uri, null, null);
1043 // Lock on mBgLock *after* the db operation
1044 synchronized(sBgLock) {
1045 switch (item.itemType) {
1046 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
1047 sBgFolders.remove(item.id);
1048 for (ItemInfo info : sBgItemsIdMap) {
1049 if (info.container == item.id) {
1050 // We are deleting a folder which still contains items that
1051 // think they are contained by that folder.
1052 String msg = (((("deleting a folder (" + item) + ") which still "🔵
1053 Log.e(TAG, msg);
1054 }
1055 }
1056 sBgWorkspaceItems.remove(item);
1057 break;
1058 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
1059 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
1060 sBgWorkspaceItems.remove(item);
1061 break;
1062 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
1063 sBgAppWidgets.remove(((LauncherAppWidgetInfo) (item)));
1064 break;
1065 }
1066 sBgItemsIdMap.remove(item.id);
1067 }
1068 }
1069 }
1070 };
1071 runOnWorkerThread(r);
1072 }
1073
1074 /**
1075 * Update the order of the workspace screens in the database. The array list contains
1076 * a list of screen ids in the order that they should appear.
1077 */
1078 void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) {
1079 final ArrayList<Long> screensCopy = new ArrayList<Long>(screens);
1080 final ContentResolver cr = context.getContentResolver();
1081 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1082 // Remove any negative screen ids -- these aren't persisted
1083 Iterator<Long> iter = screensCopy.iterator();
1084 while (iter.hasNext()) {
1085 long id = iter.next();
1086 if (id < 0) {
1087 iter.remove();
1088 }
1089 }
1090 Runnable r = new Runnable() {
1091 @Override
1092 public void run() {
1093 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1094 // Clear the table
1095 ops.add(ContentProviderOperation.newDelete(uri).build());
1096 int count = screensCopy.size();
1097 for (int i = 0; i < count; i++) {
1098 ContentValues v = new ContentValues();
1099 long screenId = screensCopy.get(i);
1100 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
1101 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
1102 ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
1103 }
1104 try {
1105 cr.applyBatch(LauncherProvider.AUTHORITY, ops);
1106 } catch (java.lang.Exception ex) {
1107 throw new RuntimeException(ex);
1108 }
1109 synchronized(sBgLock) {
1110 sBgWorkspaceScreens.clear();
1111 sBgWorkspaceScreens.addAll(screensCopy);
1112 }
1113 }
1114 };
1115 runOnWorkerThread(r);
1116 }
1117
1118 /**
1119 * Remove the contents of the specified folder from the database
1120 */
1121 public static void deleteFolderContentsFromDatabase(Context context, final FolderInfo info) {
1122 final ContentResolver cr = context.getContentResolver();
1123 Runnable r = new Runnable() {
1124 public void run() {
1125 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
1126 // Lock on mBgLock *after* the db operation
1127 synchronized(sBgLock) {
1128 sBgItemsIdMap.remove(info.id);
1129 sBgFolders.remove(info.id);
1130 sBgWorkspaceItems.remove(info);
1131 }
1132 cr.delete(LauncherSettings.Favorites.CONTENT_URI, (LauncherSettings.Favorites.CONTAINER +🔵
1133 // Lock on mBgLock *after* the db operation
1134 synchronized(sBgLock) {
1135 for (ItemInfo childInfo : info.contents) {
1136 sBgItemsIdMap.remove(childInfo.id);
1137 }
1138 }
1139 }
1140 };
1141 runOnWorkerThread(r);
1142 }
1143
1144 /**
1145 * Set this as the current Launcher activity object for the loader.
1146 */
1147 public void initialize(Callbacks callbacks) {
1148 synchronized(mLock) {
1149 // Disconnect any of the callbacks and drawables associated with ItemInfos on the
1150 // workspace to prevent leaking Launcher activities on orientation change.
1151 unbindItemInfosAndClearQueuedBindRunnables();
1152 mCallbacks = new WeakReference<Callbacks>(callbacks);
1153 }
1154 }
1155
1156 @Override
1157 public void onPackageChanged(String packageName, UserHandleCompat user) {
1158 int op = PackageUpdatedTask.OP_UPDATE;
1159 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1160 user));
1161 }
1162
1163 @Override
1164 public void onPackageRemoved(String packageName, UserHandleCompat user) {
1165 int op = PackageUpdatedTask.OP_REMOVE;
1166 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1167 user));
1168 }
1169
1170 @Override
1171 public void onPackageAdded(String packageName, UserHandleCompat user) {
1172 int op = PackageUpdatedTask.OP_ADD;
1173 enqueuePackageUpdated(new PackageUpdatedTask(op, new String[] { packageName },
1174 user));
1175 }
1176
1177 @Override
1178 public void onPackagesAvailable(String[] packageNames, UserHandleCompat user,
1179 boolean replacing) {
1180 if (!replacing) {
1181 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_ADD, packageNames,
1182 user));
1183 if (mAppsCanBeOnRemoveableStorage) {
1184 // Only rebind if we support removable storage. It catches the
1185 // case where
1186 // apps on the external sd card need to be reloaded
1187 startLoaderFromBackground();
1188 }
1189 } else {
1190 // If we are replacing then just update the packages in the list
1191 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE,
1192 packageNames, user));
1193 }
1194 }
1195
1196 @Override
1197 public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user,
1198 boolean replacing) {
1199 if (!replacing) {
1200 enqueuePackageUpdated(new PackageUpdatedTask(
1201 PackageUpdatedTask.OP_UNAVAILABLE, packageNames,
1202 user));
1203 }
1204 }
1205
1206 /**
1207 * Call from the handler for ACTION_PACKAGE_ADDED, ACTION_PACKAGE_REMOVED and
1208 * ACTION_PACKAGE_CHANGED.
1209 */
1210 @Override
1211 public void onReceive(Context context, Intent intent) {
1212 if (DEBUG_RECEIVER) {
1213 Log.d(TAG, "onReceive intent=" + intent);
1214 }
1215 final String action = intent.getAction();
1216 if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
1217 // If we have changed locale we need to clear out the labels in all apps/workspace.
1218 forceReload();
1219 } else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action) || SearchManager.IN🔵
1220 Callbacks callbacks = getCallback();
1221 if (callbacks != null) {
1222 callbacks.bindSearchablesChanged();
1223 }
1224 } else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action) || LauncherAppsCompat.A🔵
1225 forceReload();
1226 }
1227 }
1228
1229 void forceReload() {
1230 resetLoadedState(true, true);
1231
1232 // Do this here because if the launcher activity is running it will be restarted.
1233 // If it's not running startLoaderFromBackground will merely tell it that it needs
1234 // to reload.
1235 startLoaderFromBackground();
1236 }
1237
1238 public void resetLoadedState(boolean resetAllAppsLoaded, boolean resetWorkspaceLoaded) {
1239 synchronized(mLock) {
1240 // Stop any existing loaders first, so they don't set mAllAppsLoaded or
1241 // mWorkspaceLoaded to true later
1242 stopLoaderLocked();
1243 if (resetAllAppsLoaded) {
1244 mAllAppsLoaded = false;
1245 }
1246 if (resetWorkspaceLoaded) {
1247 mWorkspaceLoaded = false;
1248 }
1249 }
1250 }
1251
1252 /**
1253 * When the launcher is in the background, it's possible for it to miss paired
1254 * configuration changes. So whenever we trigger the loader from the background
1255 * tell the launcher that it needs to re-run the loader when it comes back instead
1256 * of doing it now.
1257 */
1258 public void startLoaderFromBackground() {
1259 boolean runLoader = false;
1260 Callbacks callbacks = getCallback();
1261 if (callbacks != null) {
1262 // Only actually run the loader if they're not paused.
1263 if (!callbacks.setLoadOnResume()) {
1264 runLoader = true;
1265 }
1266 }
1267 if (runLoader) {
1268 startLoader(PagedView.INVALID_RESTORE_PAGE);
1269 }
1270 }
1271
1272 /**
1273 * If there is already a loader task running, tell it to stop.
1274 */
1275 private void stopLoaderLocked() {
1276 LoaderTask oldTask = mLoaderTask;
1277 if (oldTask != null) {
1278 oldTask.stopLocked();
1279 }
1280 }
1281
1282 public boolean isCurrentCallbacks(Callbacks callbacks) {
1283 return (mCallbacks != null && mCallbacks.get() == callbacks);
1284 }
1285
1286 public void startLoader(int synchronousBindPage) {
1287 startLoader(synchronousBindPage, LOADER_FLAG_NONE);
1288 }
1289
1290 public void startLoader(int synchronousBindPage, int loadFlags) {
1291 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
1292 InstallShortcutReceiver.enableInstallQueue();
1293 synchronized(mLock) {
1294 // Clear any deferred bind-runnables from the synchronized load process
1295 // We must do this before any loading/binding is scheduled below.
1296 synchronized(mDeferredBindRunnables) {
1297 mDeferredBindRunnables.clear();
1298 }
1299 // Don't bother to start the thread if we know it's not going to do anything
1300 if ((mCallbacks != null) && (mCallbacks.get() != null)) {
1301 // If there is already one running, tell it to stop.
1302 stopLoaderLocked();
1303 mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
1304 if ((((synchronousBindPage != PagedView.INVALID_RESTORE_PAGE) && mAllAppsLoaded) && mWork🔵
1305 mLoaderTask.runBindSynchronousPage(synchronousBindPage);
1306 } else {
1307 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
1308 sWorker.post(mLoaderTask);
1309 }
1310 }
1311 }
1312 }
1313
1314 void bindRemainingSynchronousPages() {
1315 // Post the remaining side pages to be loaded
1316 if (!mDeferredBindRunnables.isEmpty()) {
1317 Runnable[] deferredBindRunnables = null;
1318 synchronized(mDeferredBindRunnables) {
1319 deferredBindRunnables = mDeferredBindRunnables.toArray(new Runnable[mDeferredBindRunnable🔵
1320 mDeferredBindRunnables.clear();
1321 }
1322 for (final Runnable r : deferredBindRunnables) {
1323 mHandler.post(r);
1324 }
1325 }
1326 // Run all the bind complete runnables after workspace is bound.
1327 if (!mBindCompleteRunnables.isEmpty()) {
1328 synchronized(mBindCompleteRunnables) {
1329 for (final Runnable r : mBindCompleteRunnables) {
1330 runOnWorkerThread(r);
1331 }
1332 mBindCompleteRunnables.clear();
1333 }
1334 }
1335 }
1336
1337 public void stopLoader() {
1338 synchronized (mLock) {
1339 if (mLoaderTask != null) {
1340 mLoaderTask.stopLocked();
1341 }
1342 }
1343 }
1344
1345 /**
1346 * Loads the workspace screen ids in an ordered list.
1347 */
1348 @Thunk
1349 static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
1350 final ContentResolver contentResolver = context.getContentResolver();
1351 final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
1352 // Get screens ordered by rank.
1353 final Cursor sc = contentResolver.query(screensUri, null, null, null, LauncherSettings.WorkspaceS🔵
1354 ArrayList<Long> screenIds = new ArrayList<Long>();
1355 try {
1356 final int idIndex = sc.getColumnIndexOrThrow(LauncherSettings.WorkspaceScreens._ID);
1357 while (sc.moveToNext()) {
1358 try {
1359 screenIds.add(sc.getLong(idIndex));
1360 } catch (java.lang.Exception e) {
1361 Launcher.addDumpLog(TAG, ("Desktop items loading interrupted" + " - invalid screens: 🔵
1362 }
1363 }
1364 } finally {
1365 sc.close();
1366 }
1367 return screenIds;
1368 }
1369
1370 public boolean isAllAppsLoaded() {
1371 return mAllAppsLoaded;
1372 }
1373
1374 boolean isLoadingWorkspace() {
1375 synchronized (mLock) {
1376 if (mLoaderTask != null) {
1377 return mLoaderTask.isLoadingWorkspace();
1378 }
1379 }
1380 return false;
1381 }
1382
1383 /**
1384 * Runnable for the thread that loads the contents of the launcher:
1385 * - workspace icons
1386 * - widgets
1387 * - all apps icons
1388 */
1389 private class LoaderTask implements Runnable {
1390 private Context mContext;
1391
1392 @Thunk
1393 boolean mIsLoadingAndBindingWorkspace;
1394
1395 private boolean mStopped;
1396
1397 @Thunk
1398 boolean mLoadAndBindStepFinished;
1399
1400 private int mFlags;
1401
1402 LoaderTask(Context context, int flags) {
1403 mContext = context;
1404 mFlags = flags;
1405 }
1406
1407 boolean isLoadingWorkspace() {
1408 return mIsLoadingAndBindingWorkspace;
1409 }
1410
1411 private void loadAndBindWorkspace() {
1412 mIsLoadingAndBindingWorkspace = true;
1413 // Load the workspace
1414 if (DEBUG_LOADERS) {
1415 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
1416 }
1417 if (!mWorkspaceLoaded) {
1418 loadWorkspace();
1419 synchronized(this) {
1420 if (mStopped) {
1421 return;
1422 }
1423 mWorkspaceLoaded = true;
1424 }
1425 }
1426 // Bind the workspace
1427 bindWorkspace(-1);
1428 }
1429
1430 private void waitForIdle() {
1431 // Wait until the either we're stopped or the other threads are done.
1432 // This way we don't start loading all apps until the workspace has settled
1433 // down.
1434 synchronized (LoaderTask.this) {
1435 final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
1436
1437 mHandler.postIdle(new Runnable() {
1438 public void run() {
1439 synchronized (LoaderTask.this) {
1440 mLoadAndBindStepFinished = true;
1441 if (DEBUG_LOADERS) {
1442 Log.d(TAG, "done with previous binding step");
1443 }
1444 LoaderTask.this.notify();
1445 }
1446 }
1447 });
1448
1449 while (!mStopped && !mLoadAndBindStepFinished) {
1450 try {
1451 // Just in case mFlushingWorkerThread changes but we aren't woken up,
1452 // wait no longer than 1sec at a time
1453 this.wait(1000);
1454 } catch (InterruptedException ex) {
1455 // Ignore
1456 }
1457 }
1458 if (DEBUG_LOADERS) {
1459 Log.d(TAG, "waited "
1460 + (SystemClock.uptimeMillis()-workspaceWaitTime)
1461 + "ms for previous step to finish binding");
1462 }
1463 }
1464 }
1465
1466 void runBindSynchronousPage(int synchronousBindPage) {
1467 if (synchronousBindPage == PagedView.INVALID_RESTORE_PAGE) {
1468 // Ensure that we have a valid page index to load synchronously
1469 throw new RuntimeException("Should not call runBindSynchronousPage() without " + "valid p🔵
1470 }
1471 if ((!mAllAppsLoaded) || (!mWorkspaceLoaded)) {
1472 // Ensure that we don't try and bind a specified page when the pages have not been
1473 // loaded already (we should load everything asynchronously in that case)
1474 throw new RuntimeException("Expecting AllApps and Workspace to be loaded");
1475 }
1476 synchronized(mLock) {
1477 if (mIsLoaderTaskRunning) {
1478 // Ensure that we are never running the background loading at this point since
1479 // we also touch the background collections
1480 throw new RuntimeException("Error! Background loading is already running");
1481 }
1482 }
1483 // XXX: Throw an exception if we are already loading (since we touch the worker thread
1484 // data structures, we can't allow any other thread to touch that data, but because
1485 // this call is synchronous, we can get away with not locking).
1486 // The LauncherModel is static in the LauncherAppState and mHandler may have queued
1487 // operations from the previous activity. We need to ensure that all queued operations
1488 // are executed before any synchronous binding work is done.
1489 mHandler.flush();
1490 // Divide the set of loaded items into those that we are binding synchronously, and
1491 // everything else that is to be bound normally (asynchronously).
1492 bindWorkspace(synchronousBindPage);
1493 // XXX: For now, continue posting the binding of AllApps as there are other issues that
1494 // arise from that.
1495 onlyBindAllApps();
1496 }
1497
1498 public void run() {
1499 synchronized(mLock) {
1500 if (mStopped) {
1501 return;
1502 }
1503 mIsLoaderTaskRunning = true;
1504 }
1505 // Optimize for end-user experience: if the Launcher is up and // running with the
1506 // All Apps interface in the foreground, load All Apps first. Otherwise, load the
1507 // workspace first (default).
1508 keep_running : {
1509 if (DEBUG_LOADERS) {
1510 Log.d(TAG, "step 1: loading workspace");
1511 }
1512 loadAndBindWorkspace();
1513 if (mStopped) {
1514 break keep_running;
1515 }
1516 waitForIdle();
1517 // second step
1518 if (DEBUG_LOADERS) {
1519 Log.d(TAG, "step 2: loading all apps");
1520 }
1521 loadAndBindAllApps();
1522 }
1523 // Clear out this reference, otherwise we end up holding it until all of the
1524 // callback runnables are done.
1525 mContext = null;
1526 synchronized(mLock) {
1527 // If we are still the last one to be scheduled, remove ourselves.
1528 if (mLoaderTask == this) {
1529 mLoaderTask = null;
1530 }
1531 mIsLoaderTaskRunning = false;
1532 mHasLoaderCompletedOnce = true;
1533 }
1534 }
1535
1536 public void stopLocked() {
1537 synchronized (LoaderTask.this) {
1538 mStopped = true;
1539 this.notify();
1540 }
1541 }
1542
1543 /**
1544 * Gets the callbacks object. If we've been stopped, or if the launcher object
1545 * has somehow been garbage collected, return null instead. Pass in the Callbacks
1546 * object that was around when the deferred message was scheduled, and if there's
1547 * a new Callbacks object around then also return null. This will save us from
1548 * calling onto it with data that will be ignored.
1549 */
1550 Callbacks tryGetCallbacks(Callbacks oldCallbacks) {
1551 synchronized (mLock) {
1552 if (mStopped) {
1553 return null;
1554 }
1555
1556 if (mCallbacks == null) {
1557 return null;
1558 }
1559
1560 final Callbacks callbacks = mCallbacks.get();
1561 if (callbacks != oldCallbacks) {
1562 return null;
1563 }
1564 if (callbacks == null) {
1565 Log.w(TAG, "no mCallbacks");
1566 return null;
1567 }
1568
1569 return callbacks;
1570 }
1571 }
1572
1573 // check & update map of what's occupied; used to discard overlapping/invalid items
1574 private boolean checkItemPlacement(LongArrayMap<ItemInfo[][]> occupied, ItemInfo item) {
1575 LauncherAppState app = LauncherAppState.getInstance();
1576 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1577 final int countX = ((int) (profile.numColumns));
1578 final int countY = ((int) (profile.numRows));
1579 long containerIndex = item.screenId;
1580 if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
1581 // Return early if we detect that an item is under the hotseat button
1582 if ((mCallbacks == null) || mCallbacks.get().isAllAppsButtonRank(((int) (item.screenId)))🔵
1583 Log.e(TAG, ((((((("Error loading shortcut into hotseat " + item) + " into position ("🔵
1584 return false;
1585 }
1586 final ItemInfo[][] hotseatItems = occupied.get(((long) (LauncherSettings.Favorites.CONTAI🔵
1587 if (item.screenId >= profile.numHotseatIcons) {
1588 Log.e(TAG, ((((("Error loading shortcut " + item) + " into hotseat position ") + item🔵
1589 return false;
1590 }
1591 if (hotseatItems != null) {
1592 if (hotseatItems[((int) (item.screenId))][0] != null) {
1593 Log.e(TAG, (((((((("Error loading shortcut into hotseat " + item) + " into positi🔵
1594 return false;
1595 } else {
1596 hotseatItems[((int) (item.screenId))][0] = item;
1597 return true;
1598 }
1599 } else {
1600 final ItemInfo[][] items = new ItemInfo[((int) (profile.numHotseatIcons))][1];
1601 items[((int) (item.screenId))][0] = item;
1602 occupied.put(((long) (LauncherSettings.Favorites.CONTAINER_HOTSEAT)), items);
1603 return true;
1604 }
1605 } else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
1606 // Skip further checking if it is not the hotseat or workspace container
1607 return true;
1608 }
1609 if (!occupied.containsKey(item.screenId)) {
1610 ItemInfo[][] items = new ItemInfo[countX + 1][countY + 1];
1611 occupied.put(item.screenId, items);
1612 }
1613 final ItemInfo[][] screens = occupied.get(item.screenId);
1614 if (((((item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) && (item.cellX < 0)) 🔵
1615 Log.e(TAG, ((((((((((((("Error loading shortcut " + item) + " into cell (") + containerIn🔵
1616 return false;
1617 }
1618 // Check if any workspace icons overlap with each other
1619 for (int x = item.cellX; x < (item.cellX + item.spanX); x++) {
1620 for (int y = item.cellY; y < (item.cellY + item.spanY); y++) {
1621 if (screens[x][y] != null) {
1622 Log.e(TAG, (((((((((("Error loading shortcut " + item) + " into cell (") + contai🔵
1623 return false;
1624 }
1625 }
1626 }
1627 for (int x = item.cellX; x < (item.cellX + item.spanX); x++) {
1628 for (int y = item.cellY; y < (item.cellY + item.spanY); y++) {
1629 screens[x][y] = item;
1630 }
1631 }
1632 return true;
1633 }
1634
1635 /** Clears all the sBg data structures */
1636 private void clearSBgDataStructures() {
1637 synchronized(sBgLock) {
1638 sBgWorkspaceItems.clear();
1639 sBgAppWidgets.clear();
1640 sBgFolders.clear();
1641 sBgItemsIdMap.clear();
1642 sBgWorkspaceScreens.clear();
1643 }
1644 }
1645
1646 private void loadWorkspace() {
1647 final long t = (DEBUG_LOADERS) ? SystemClock.uptimeMillis() : 0;
1648 final Context context = mContext;
1649 final ContentResolver contentResolver = context.getContentResolver();
1650 final PackageManager manager = context.getPackageManager();
1651 final boolean isSafeMode = manager.isSafeMode();
1652 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
1653 final boolean isSdCardReady = context.registerReceiver(null, new IntentFilter(StartupReceiver🔵
1654 LauncherAppState app = LauncherAppState.getInstance();
1655 InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
1656 int countX = ((int) (profile.numColumns));
1657 int countY = ((int) (profile.numRows));
1658 if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
1659 Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true);
1660 LauncherAppState.getLauncherProvider().deleteDatabase();
1661 }
1662 if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
1663 // append the user's Launcher2 shortcuts
1664 Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
1665 LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
1666 } else {
1667 // Make sure the default workspace is loaded
1668 Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
1669 LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
1670 }
1671 synchronized(sBgLock) {
1672 clearSBgDataStructures();
1673 final HashMap<String, Integer> installingPkgs = PackageInstallerCompat.getInstance(mConte🔵
1674 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
1675 final ArrayList<Long> restoredRows = new ArrayList<Long>();
1676 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
1677 if (DEBUG_LOADERS) {
1678 Log.d(TAG, "loading model from " + contentUri);
1679 }
1680 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
1681 // +1 for the hotseat (it can be larger than the workspace)
1682 // Load workspace in reverse order to ensure that latest items are loaded first (and
1683 // before any earlier duplicates)
1684 final LongArrayMap<ItemInfo[][]> occupied = new LongArrayMap<>();
1685 try {
1686 final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
1687 final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
1688 final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE);
1689 final int containerIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAIN🔵
1690 final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYP🔵
1691 final int appWidgetIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.APPWI🔵
1692 final int appWidgetProviderIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites🔵
1693 final int screenIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
1694 final int cellXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
1695 final int cellYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
1696 final int spanXIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
1697 final int spanYIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
1698 final int rankIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.RANK);
1699 final int restoredIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.RESTORED🔵
1700 final int profileIdIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE🔵
1701 final int optionsIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.OPTIONS);
1702 final CursorIconInfo cursorIconInfo = new CursorIconInfo(c);
1703 final LongSparseArray<UserHandleCompat> allUsers = new LongSparseArray<>();
1704 for (UserHandleCompat user : mUserManager.getUserProfiles()) {
1705 allUsers.put(mUserManager.getSerialNumberForUser(user), user);
1706 }
1707 ShortcutInfo info;
1708 String intentDescription;
1709 LauncherAppWidgetInfo appWidgetInfo;
1710 int container;
1711 long id;
1712 long serialNumber;
1713 Intent intent;
1714 UserHandleCompat user;
1715 while ((!mStopped) && c.moveToNext()) {
1716 try {
1717 int itemType = c.getInt(itemTypeIndex);
1718 boolean restored = 0 != c.getInt(restoredIndex);
1719 boolean allowMissingTarget = false;
1720 container = c.getInt(containerIndex);
1721 switch (itemType) {
1722 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION :
1723 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT :
1724 id = c.getLong(idIndex);
1725 intentDescription = c.getString(intentIndex);
1726 serialNumber = c.getInt(profileIdIndex);
1727 user = allUsers.get(serialNumber);
1728 int promiseType = c.getInt(restoredIndex);
1729 int disabledState = 0;
1730 boolean itemReplaced = false;
1731 if (user == null) {
1732 // User has been deleted remove the item.
1733 itemsToRemove.add(id);
1734 continue;
1735 }
1736 try {
1737 intent = Intent.parseUri(intentDescription, 0);
1738 ComponentName cn = intent.getComponent();
1739 if ((cn != null) && (cn.getPackageName() != null)) {
1740 boolean validPkg = launcherApps.isPackageEnabledForProfile(cn🔵
1741 boolean validComponent = validPkg && launcherApps.isActivityE🔵
1742 if (validComponent) {
1743 if (restored) {
1744 // no special handling necessary for this item
1745 restoredRows.add(id);
1746 restored = false;
1747 }
1748 } else if (validPkg) {
1749 intent = null;
1750 if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 🔵
1751 // We allow auto install apps to have their intent
1752 // updated after an install.
1753 intent = manager.getLaunchIntentForPackage(cn.getPack🔵
1754 if (intent != null) {
1755 ContentValues values = new ContentValues();
1756 values.put(LauncherSettings.Favorites.INTENT, int🔵
1757 updateItem(id, values);
1758 }
1759 }
1760 if (intent == null) {
1761 // The app is installed but the component is no
1762 // longer available.
1763 Launcher.addDumpLog(TAG, "Invalid component removed: 🔵
1764 itemsToRemove.add(id);
1765 continue;
1766 } else {
1767 // no special handling necessary for this item
1768 restoredRows.add(id);
1769 restored = false;
1770 }
1771 } else if (restored) {
1772 // Package is not yet available but might be
1773 // installed later.
1774 Launcher.addDumpLog(TAG, "package not yet restored: " + c🔵
1775 if ((promiseType & ShortcutInfo.FLAG_RESTORE_STARTED) != 🔵
1776 // Restore has started once.
1777 } else if (installingPkgs.containsKey(cn.getPackageName()🔵
1778 // App restore has started. Update the flag
1779 promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED;
1780 ContentValues values = new ContentValues();
1781 values.put(LauncherSettings.Favorites.RESTORED, promi🔵
1782 updateItem(id, values);
1783 } else if ((promiseType & ShortcutInfo.FLAG_RESTORED_APP_🔵
1784 // This is a common app. Try to replace this.
1785 int appType = CommonAppTypeParser.decodeItemTypeFromF🔵
1786 CommonAppTypeParser parser = new CommonAppTypeParser(🔵
1787 if (parser.findDefaultApp()) {
1788 // Default app found. Replace it.
1789 intent = parser.parsedIntent;
1790 cn = intent.getComponent();
1791 ContentValues values = parser.parsedValues;
1792 values.put(LauncherSettings.Favorites.RESTORED, 0🔵
1793 updateItem(id, values);
1794 restored = false;
1795 itemReplaced = true;
1796 } else if (REMOVE_UNRESTORED_ICONS) {
1797 Launcher.addDumpLog(TAG, "Unrestored package remo🔵
1798 itemsToRemove.add(id);
1799 continue;
1800 }
1801 } else if (REMOVE_UNRESTORED_ICONS) {
1802 Launcher.addDumpLog(TAG, "Unrestored package removed:🔵
1803 itemsToRemove.add(id);
1804 continue;
1805 }
1806 } else if (launcherApps.isAppEnabled(manager, cn.getPackageNa🔵
1807 // Package is present but not available.
1808 allowMissingTarget = true;
1809 disabledState = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
1810 } else if (!isSdCardReady) {
1811 // SdCard is not ready yet. Package might get available,
1812 // once it is ready.
1813 Launcher.addDumpLog(TAG, ("Invalid package: " + cn) + " (🔵
1814 HashSet<String> pkgs = sPendingPackages.get(user);
1815 if (pkgs == null) {
1816 pkgs = new HashSet<String>();
1817 sPendingPackages.put(user, pkgs);
1818 }
1819 pkgs.add(cn.getPackageName());
1820 allowMissingTarget = true;
1821 // Add the icon on the workspace anyway.
1822 } else {
1823 // Do not wait for external media load anymore.
1824 // Log the invalid package, and remove it
1825 Launcher.addDumpLog(TAG, "Invalid package removed: " + cn🔵
1826 itemsToRemove.add(id);
1827 continue;
1828 }
1829 } else if (cn == null) {
1830 // For shortcuts with no component, keep them as they are
1831 restoredRows.add(id);
1832 restored = false;
1833 }
1834 } catch (URISyntaxException e) {
1835 Launcher.addDumpLog(TAG, "Invalid uri: " + intentDescription, tru🔵
1836 continue;
1837 }
1838 boolean useLowResIcon = (container >= 0) && (c.getInt(rankIndex) >= F🔵
1839 if (itemReplaced) {
1840 if (user.equals(UserHandleCompat.myUserHandle())) {
1841 info = getAppShortcutInfo(manager, intent, user, context, nul🔵
1842 } else {
1843 // Don't replace items for other profiles.
1844 itemsToRemove.add(id);
1845 continue;
1846 }
1847 } else if (restored) {
1848 if (user.equals(UserHandleCompat.myUserHandle())) {
1849 Launcher.addDumpLog(TAG, "constructing info for partially res🔵
1850 info = getRestoredItemInfo(c, titleIndex, intent, promiseType🔵
1851 intent = getRestoredItemIntent(c, context, intent);
1852 } else {
1853 // Don't restore items for other profiles.
1854 itemsToRemove.add(id);
1855 continue;
1856 }
1857 } else if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATI🔵
1858 info = getAppShortcutInfo(manager, intent, user, context, c, curs🔵
1859 } else {
1860 info = getShortcutInfo(c, context, titleIndex, cursorIconInfo);
1861 // App shortcuts that used to be automatically added to Launcher
1862 // didn't always have the correct intent flags set, so do that
1863 // here
1864 if ((((intent.getAction() != null) && (intent.getCategories() != 🔵
1865 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_A🔵
1866 }
1867 }
1868 if (info != null) {
1869 info.id = id;
1870 info.intent = intent;
1871 info.container = container;
1872 info.screenId = c.getInt(screenIndex);
1873 info.cellX = c.getInt(cellXIndex);
1874 info.cellY = c.getInt(cellYIndex);
1875 info.rank = c.getInt(rankIndex);
1876 info.spanX = 1;
1877 info.spanY = 1;
1878 info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
1879 if (info.promisedIntent != null) {
1880 info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNu🔵
1881 }
1882 info.isDisabled = disabledState;
1883 if (isSafeMode && (!Utilities.isSystemApp(context, intent))) {
1884 info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE;
1885 }
1886 // check & update map of what's occupied
1887 if (!checkItemPlacement(occupied, info)) {
1888 itemsToRemove.add(id);
1889 break;
1890 }
1891 if (restored) {
1892 ComponentName cn = info.getTargetComponent();
1893 if (cn != null) {
1894 Integer progress = installingPkgs.get(cn.getPackageName()🔵
1895 if (progress != null) {
1896 info.setInstallProgress(progress);
1897 } else {
1898 info.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACT🔵
1899 }
1900 }
1901 }
1902 switch (container) {
1903 case LauncherSettings.Favorites.CONTAINER_DESKTOP :
1904 case LauncherSettings.Favorites.CONTAINER_HOTSEAT :
1905 sBgWorkspaceItems.add(info);
1906 break;
1907 default :
1908 // Item is in a user folder
1909 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, cont🔵
1910 folderInfo.add(info);
1911 break;
1912 }
1913 sBgItemsIdMap.put(info.id, info);
1914 } else {
1915 throw new RuntimeException("Unexpected null ShortcutInfo");
1916 }
1917 break;
1918 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER :
1919 id = c.getLong(idIndex);
1920 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
1921 // Do not trim the folder label, as is was set by the user.
1922 folderInfo.title = c.getString(titleIndex);
1923 folderInfo.id = id;
1924 folderInfo.container = container;
1925 folderInfo.screenId = c.getInt(screenIndex);
1926 folderInfo.cellX = c.getInt(cellXIndex);
1927 folderInfo.cellY = c.getInt(cellYIndex);
1928 folderInfo.spanX = 1;
1929 folderInfo.spanY = 1;
1930 folderInfo.options = c.getInt(optionsIndex);
1931 // check & update map of what's occupied
1932 if (!checkItemPlacement(occupied, folderInfo)) {
1933 itemsToRemove.add(id);
1934 break;
1935 }
1936 switch (container) {
1937 case LauncherSettings.Favorites.CONTAINER_DESKTOP :
1938 case LauncherSettings.Favorites.CONTAINER_HOTSEAT :
1939 sBgWorkspaceItems.add(folderInfo);
1940 break;
1941 }
1942 if (restored) {
1943 // no special handling required for restored folders
1944 restoredRows.add(id);
1945 }
1946 sBgItemsIdMap.put(folderInfo.id, folderInfo);
1947 sBgFolders.put(folderInfo.id, folderInfo);
1948 break;
1949 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET :
1950 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET :
1951 // Read all Launcher-specific widget details
1952 boolean customWidget = itemType == LauncherSettings.Favorites.ITEM_TY🔵
1953 id = c.getLong(idIndex);
1954
1955 <<<<<<< LEFT
1956 user = mUserManager.getUserForSerialNumber(serialNumber);
1957 if (user == null) {
1958 // User has been deleted remove the item.
1959 itemsToRemove.add(id);
1960 continue;
1961 }
1962 ||||||| BASE
1963 /*d94z9sk0k4hf9j3ijd - note the base isn't actually empty, spork simply doesn't generate a base - gd930kw🔵
1964 =======
1965
1966 int appWidgetId = c.getInt(appWidgetIdIndex);
1967 serialNumber= c.getLong(profileIdIndex);
1968 user = mUserManager.getUserForSerialNumber(serialNumber);
1969 if (user == null) {
1970 // User has been deleted remove the item.
1971 itemsToRemove.add(id);
1972 continue;
1973 }
1974 String savedProvider = c.getString(appWidgetProviderIndex);
1975 >>>>>>> RIGHT
1976
1977 final ComponentName component = ComponentName.unflattenFromString(sav🔵
1978 final int restoreStatus = c.getInt(restoredIndex);
1979 final boolean isIdValid = (restoreStatus & LauncherAppWidgetInfo.FLAG🔵
1980 final boolean wasProviderReady = (restoreStatus & LauncherAppWidgetIn🔵
1981 final LauncherAppWidgetProviderInfo provider = LauncherModel.getProvi🔵
1982 final boolean isProviderReady = isValidProvider(provider);
1983 if ((((!isSafeMode) && (!customWidget)) && wasProviderReady) && (!isP🔵
1984 String log = ((("Deleting widget that isn't installed anymore: " 🔵
1985 Log.e(TAG, log);
1986 Launcher.addDumpLog(TAG, log, false);
1987 itemsToRemove.add(id);
1988 } else {
1989 if (isProviderReady) {
1990 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, provid🔵
1991 int status = restoreStatus;
1992 if (!wasProviderReady) {
1993 // If provider was not previously ready, update the
1994 // status and UI flag.
1995 // Id would be valid only if the widget restore broadcast🔵
1996 if (isIdValid) {
1997 status = LauncherAppWidgetInfo.RESTORE_COMPLETED;
1998 } else {
1999 status &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_RE🔵
2000 }
2001 }
2002 appWidgetInfo.restoreStatus = status;
2003 } else {
2004 Log.v(TAG, (((("Widget restore pending id=" + id) + " appWidg🔵
2005 appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, compon🔵
2006 appWidgetInfo.restoreStatus = restoreStatus;
2007 Integer installProgress = installingPkgs.get(component.getPac🔵
2008 if ((restoreStatus & LauncherAppWidgetInfo.FLAG_RESTORE_START🔵
2009 // Restore has started once.
2010 } else if (installProgress != null) {
2011 // App restore has started. Update the flag
2012 appWidgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG🔵
2013 } else if (REMOVE_UNRESTORED_ICONS && (!isSafeMode)) {
2014 Launcher.addDumpLog(TAG, "Unrestored widget removed: " + 🔵
2015 itemsToRemove.add(id);
2016 continue;
2017 }
2018 appWidgetInfo.installProgress = (installProgress == null) ? 0🔵
2019 }
2020 appWidgetInfo.id = id;
2021 appWidgetInfo.screenId = c.getInt(screenIndex);
2022 appWidgetInfo.cellX = c.getInt(cellXIndex);
2023 appWidgetInfo.cellY = c.getInt(cellYIndex);
2024 appWidgetInfo.spanX = c.getInt(spanXIndex);
2025 appWidgetInfo.spanY = c.getInt(spanYIndex);
2026 if ((container != LauncherSettings.Favorites.CONTAINER_DESKTOP) &🔵
2027 Log.e(TAG, "Widget found where container != " + "CONTAINER_DE🔵
2028 continue;
2029 }
2030 appWidgetInfo.container = container;
2031 // check & update map of what's occupied
2032 if (!checkItemPlacement(occupied, appWidgetInfo)) {
2033 itemsToRemove.add(id);
2034 break;
2035 }
2036 if (!customWidget) {
2037 String providerName = appWidgetInfo.providerName.flattenToStr🔵
2038 if ((!providerName.equals(savedProvider)) || (appWidgetInfo.r🔵
2039 ContentValues values = new ContentValues();
2040 values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,🔵
2041 values.put(LauncherSettings.Favorites.RESTORED, appWidget🔵
2042 updateItem(id, values);
2043 }
2044 }
2045 sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
2046 sBgAppWidgets.add(appWidgetInfo);
2047 }
2048 break;
2049 }
2050 } catch (java.lang.Exception e) {
2051 Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
2052 }
2053 }
2054 } finally {
2055 if (c != null) {
2056 c.close();
2057 }
2058 }
2059 // Break early if we've stopped loading
2060 if (mStopped) {
2061 clearSBgDataStructures();
2062 return;
2063 }
2064 if (itemsToRemove.size() > 0) {
2065 // Remove dead items
2066 contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI, Utilities.createDbSele🔵
2067 if (DEBUG_LOADERS) {
2068 Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery(LauncherSettings.Favor🔵
2069 }
2070 // Remove any empty folder
2071 for (long folderId : LauncherAppState.getLauncherProvider().deleteEmptyFolders()) {
2072 sBgWorkspaceItems.remove(sBgFolders.get(folderId));
2073 sBgFolders.remove(folderId);
2074 sBgItemsIdMap.remove(folderId);
2075 }
2076 }
2077 if (restoredRows.size() > 0) {
2078 // Update restored items that no longer require special handling
2079 ContentValues values = new ContentValues();
2080 values.put(LauncherSettings.Favorites.RESTORED, 0);
2081 contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values, Utilities.crea🔵
2082 }
2083 if ((!isSdCardReady) && (!sPendingPackages.isEmpty())) {
2084 context.registerReceiver(new AppsAvailabilityCheck(), new IntentFilter(StartupReceive🔵
2085 }
2086 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));
2087 // Remove any empty screens
2088 ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
2089 for (ItemInfo item : sBgItemsIdMap) {
2090 long screenId = item.screenId;
2091 if ((item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) && unusedScreens🔵
2092 unusedScreens.remove(screenId);
2093 }
2094 }
2095 // If there are any empty screens remove them, and update.
2096 if (unusedScreens.size() != 0) {
2097 sBgWorkspaceScreens.removeAll(unusedScreens);
2098 updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
2099 }
2100 if (DEBUG_LOADERS) {
2101 Log.d(TAG, ("loaded workspace in " + (SystemClock.uptimeMillis() - t)) + "ms");
2102 Log.d(TAG, "workspace layout: ");
2103 int nScreens = occupied.size();
2104 for (int y = 0; y < countY; y++) {
2105 String line = "";
2106 for (int i = 0; i < nScreens; i++) {
2107 long screenId = occupied.keyAt(i);
2108 if (screenId > 0) {
2109 line += " | ";
2110 }
2111 ItemInfo[][] screen = occupied.valueAt(i);
2112 for (int x = 0; x < countX; x++) {
2113 if ((x < screen.length) && (y < screen[x].length)) {
2114 line += (screen[x][y] != null) ? "#" : ".";
2115 } else {
2116 line += "!";
2117 }
2118 }
2119 }
2120 Log.d(TAG, ("[ " + line) + " ]");
2121 }
2122 }
2123 }
2124 }
2125
2126 /**
2127 * Partially updates the item without any notification. Must be called on the worker thread.
2128 */
2129 private void updateItem(long itemId, ContentValues update) {
2130 mContext.getContentResolver().update(LauncherSettings.Favorites.CONTENT_URI, update, BaseColu🔵
2131 }
2132
2133 /** Filters the set of items who are directly or indirectly (via another container) on the
2134 * specified screen. */
2135 private void filterCurrentWorkspaceItems(long currentScreenId,
2136 ArrayList<ItemInfo> allWorkspaceItems,
2137 ArrayList<ItemInfo> currentScreenItems,
2138 ArrayList<ItemInfo> otherScreenItems) {
2139 // Purge any null ItemInfos
2140 Iterator<ItemInfo> iter = allWorkspaceItems.iterator();
2141 while (iter.hasNext()) {
2142 ItemInfo i = iter.next();
2143 if (i == null) {
2144 iter.remove();
2145 }
2146 }
2147
2148 // Order the set of items by their containers first, this allows use to walk through the
2149 // list sequentially, build up a list of containers that are in the specified screen,
2150 // as well as all items in those containers.
2151 Set<Long> itemsOnScreen = new HashSet<Long>();
2152 Collections.sort(allWorkspaceItems, new Comparator<ItemInfo>() {
2153 @Override
2154 public int compare(ItemInfo lhs, ItemInfo rhs) {
2155 return (int) (lhs.container - rhs.container);
2156 }
2157 });
2158 for (ItemInfo info : allWorkspaceItems) {
2159 if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2160 if (info.screenId == currentScreenId) {
2161 currentScreenItems.add(info);
2162 itemsOnScreen.add(info.id);
2163 } else {
2164 otherScreenItems.add(info);
2165 }
2166 } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
2167 currentScreenItems.add(info);
2168 itemsOnScreen.add(info.id);
2169 } else {
2170 if (itemsOnScreen.contains(info.container)) {
2171 currentScreenItems.add(info);
2172 itemsOnScreen.add(info.id);
2173 } else {
2174 otherScreenItems.add(info);
2175 }
2176 }
2177 }
2178 }
2179
2180 /** Filters the set of widgets which are on the specified screen. */
2181 private void filterCurrentAppWidgets(long currentScreenId,
2182 ArrayList<LauncherAppWidgetInfo> appWidgets,
2183 ArrayList<LauncherAppWidgetInfo> currentScreenWidgets,
2184 ArrayList<LauncherAppWidgetInfo> otherScreenWidgets) {
2185
2186 for (LauncherAppWidgetInfo widget : appWidgets) {
2187 if (widget == null) continue;
2188 if (widget.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
2189 widget.screenId == currentScreenId) {
2190 currentScreenWidgets.add(widget);
2191 } else {
2192 otherScreenWidgets.add(widget);
2193 }
2194 }
2195 }
2196
2197 /** Filters the set of folders which are on the specified screen. */
2198 private void filterCurrentFolders(long currentScreenId, LongArrayMap<ItemInfo> itemsIdMap, LongAr🔵
2199 int total = folders.size();
2200 for (int i = 0; i < total; i++) {
2201 long id = folders.keyAt(i);
2202 FolderInfo folder = folders.valueAt(i);
2203 ItemInfo info = itemsIdMap.get(id);
2204 if ((info == null) || (folder == null)) {
2205 continue;
2206 }
2207 if ((info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) && (info.screenId ==🔵
2208 currentScreenFolders.put(id, folder);
2209 } else {
2210 otherScreenFolders.put(id, folder);
2211 }
2212 }
2213 }
2214
2215 /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to
2216 * right) */
2217 private void sortWorkspaceItemsSpatially(ArrayList<ItemInfo> workspaceItems) {
2218 final LauncherAppState app = LauncherAppState.getInstance();
2219 final InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
2220 // XXX: review this
2221 Collections.sort(workspaceItems, new Comparator<ItemInfo>() {
2222 @Override
2223 public int compare(ItemInfo lhs, ItemInfo rhs) {
2224 int cellCountX = ((int) (profile.numColumns));
2225 int cellCountY = ((int) (profile.numRows));
2226 int screenOffset = cellCountX * cellCountY;
2227 int containerOffset = screenOffset * (Launcher.SCREEN_COUNT + 1);// +1 hotseat
2228
2229 long lr = (((lhs.container * containerOffset) + (lhs.screenId * screenOffset)) + (lhs🔵
2230 long rr = (((rhs.container * containerOffset) + (rhs.screenId * screenOffset)) + (rhs🔵
2231 return ((int) (lr - rr));
2232 }
2233 });
2234 }
2235
2236 private void bindWorkspaceScreens(final Callbacks oldCallbacks, final ArrayList<Long> orderedScre🔵
2237 final Runnable r = new Runnable() {
2238 @Override
2239 public void run() {
2240 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2241 if (callbacks != null) {
2242 callbacks.bindScreens(orderedScreens);
2243 }
2244 }
2245 };
2246 runOnMainThread(r);
2247 }
2248
2249 private void bindWorkspaceItems(final Callbacks oldCallbacks, final ArrayList<ItemInfo> workspace🔵
2250 final boolean postOnMainThread = deferredBindRunnables != null;
2251 // Bind the workspace items
2252 int N = workspaceItems.size();
2253 for (int i = 0; i < N; i += ITEMS_CHUNK) {
2254 final int start = i;
2255 final int chunkSize = ((i + ITEMS_CHUNK) <= N) ? ITEMS_CHUNK : N - i;
2256 final Runnable r = new Runnable() {
2257 @Override
2258 public void run() {
2259 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2260 if (callbacks != null) {
2261 callbacks.bindItems(workspaceItems, start, start + chunkSize, false);
2262 }
2263 }
2264 };
2265 if (postOnMainThread) {
2266 synchronized(deferredBindRunnables) {
2267 deferredBindRunnables.add(r);
2268 }
2269 } else {
2270 runOnMainThread(r);
2271 }
2272 }
2273 // Bind the folders
2274 if (!folders.isEmpty()) {
2275 final Runnable r = new Runnable() {
2276 public void run() {
2277 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2278 if (callbacks != null) {
2279 callbacks.bindFolders(folders);
2280 }
2281 }
2282 };
2283 if (postOnMainThread) {
2284 synchronized(deferredBindRunnables) {
2285 deferredBindRunnables.add(r);
2286 }
2287 } else {
2288 runOnMainThread(r);
2289 }
2290 }
2291 // Bind the widgets, one at a time
2292 N = appWidgets.size();
2293 for (int i = 0; i < N; i++) {
2294 final LauncherAppWidgetInfo widget = appWidgets.get(i);
2295 final Runnable r = new Runnable() {
2296 public void run() {
2297 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2298 if (callbacks != null) {
2299 callbacks.bindAppWidget(widget);
2300 }
2301 }
2302 };
2303 if (postOnMainThread) {
2304 deferredBindRunnables.add(r);
2305 } else {
2306 runOnMainThread(r);
2307 }
2308 }
2309 }
2310
2311 /**
2312 * Binds all loaded data to actual views on the main thread.
2313 */
2314 private void bindWorkspace(int synchronizeBindPage) {
2315 final long t = SystemClock.uptimeMillis();
2316 Runnable r;
2317 // Don't use these two variables in any of the callback runnables.
2318 // Otherwise we hold a reference to them.
2319 final Callbacks oldCallbacks = mCallbacks.get();
2320 if (oldCallbacks == null) {
2321 // This launcher has exited and nobody bothered to tell us. Just bail.
2322 Log.w(TAG, "LoaderTask running with no launcher");
2323 return;
2324 }
2325 // Save a copy of all the bg-thread collections
2326 ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
2327 ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<LauncherAppWidgetInfo>();
2328 ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
2329 final LongArrayMap<FolderInfo> folders;
2330 final LongArrayMap<ItemInfo> itemsIdMap;
2331 synchronized(sBgLock) {
2332 workspaceItems.addAll(sBgWorkspaceItems);
2333 appWidgets.addAll(sBgAppWidgets);
2334 orderedScreenIds.addAll(sBgWorkspaceScreens);
2335 folders = sBgFolders.clone();
2336 itemsIdMap = sBgItemsIdMap.clone();
2337 }
2338 final boolean isLoadingSynchronously = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
2339 int currScreen = (isLoadingSynchronously) ? synchronizeBindPage : oldCallbacks.getCurrentWork🔵
2340 if (currScreen >= orderedScreenIds.size()) {
2341 // There may be no workspace screens (just hotseat items and an empty page).
2342 currScreen = PagedView.INVALID_RESTORE_PAGE;
2343 }
2344 final int currentScreen = currScreen;
2345 final long currentScreenId = (currentScreen < 0) ? INVALID_SCREEN_ID : orderedScreenIds.get(c🔵
2346 // Load all the items that are on the current page first (and in the process, unbind
2347 // all the existing workspace items before we call startBinding() below.
2348 unbindWorkspaceItemsOnMainThread();
2349 // Separate the items that are on the current screen, and all the other remaining items
2350 ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
2351 ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
2352 ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
2353 ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<LauncherAppWidgetInfo>();
2354 LongArrayMap<FolderInfo> currentFolders = new LongArrayMap<>();
2355 LongArrayMap<FolderInfo> otherFolders = new LongArrayMap<>();
2356 filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, otherWork🔵
2357 filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, otherAppWidgets);
2358 filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders, otherFolders);
2359 sortWorkspaceItemsSpatially(currentWorkspaceItems);
2360 sortWorkspaceItemsSpatially(otherWorkspaceItems);
2361 // Tell the workspace that we're about to start binding items
2362 r = new Runnable() {
2363 public void run() {
2364 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2365 if (callbacks != null) {
2366 callbacks.startBinding();
2367 }
2368 }
2369 };
2370 runOnMainThread(r);
2371 bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
2372 // Load items on the current page
2373 bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, currentFolders, nu🔵
2374 if (isLoadingSynchronously) {
2375 r = new Runnable() {
2376 public void run() {
2377 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2378 if ((callbacks != null) && (currentScreen != PagedView.INVALID_RESTORE_PAGE)) {
2379 callbacks.onPageBoundSynchronously(currentScreen);
2380 }
2381 }
2382 };
2383 runOnMainThread(r);
2384 }
2385 // Load all the remaining pages (if we are loading synchronously, we want to defer this
2386 // work until after the first render)
2387 synchronized(mDeferredBindRunnables) {
2388 mDeferredBindRunnables.clear();
2389 }
2390 bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders, isLoadin🔵
2391 // Tell the workspace that we're done binding items
2392 r = new Runnable() {
2393 public void run() {
2394 Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2395 if (callbacks != null) {
2396 callbacks.finishBindingItems();
2397 }
2398 // If we're profiling, ensure this is the last thing in the queue.
2399 if (DEBUG_LOADERS) {
2400 Log.d(TAG, ("bound workspace in " + (SystemClock.uptimeMillis() - t)) + "ms");
2401 }
2402 mIsLoadingAndBindingWorkspace = false;
2403 }
2404 };
2405 if (isLoadingSynchronously) {
2406 synchronized(mDeferredBindRunnables) {
2407 mDeferredBindRunnables.add(r);
2408 }
2409 } else {
2410 runOnMainThread(r);
2411 }
2412 }
2413
2414 private void loadAndBindAllApps() {
2415 if (DEBUG_LOADERS) {
2416 Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
2417 }
2418 if (!mAllAppsLoaded) {
2419 loadAllApps();
2420 synchronized(this) {
2421 if (mStopped) {
2422 return;
2423 }
2424 }
2425 updateIconCache();
2426 synchronized(this) {
2427 if (mStopped) {
2428 return;
2429 }
2430 mAllAppsLoaded = true;
2431 }
2432 } else {
2433 onlyBindAllApps();
2434 }
2435 }
2436
2437 private void updateIconCache() {
2438 // Ignore packages which have a promise icon.
2439 HashSet<String> packagesToIgnore = new HashSet<>();
2440 synchronized(sBgLock) {
2441 for (ItemInfo info : sBgItemsIdMap) {
2442 if (info instanceof ShortcutInfo) {
2443 ShortcutInfo si = ((ShortcutInfo) (info));
2444 if (si.isPromise() && (si.getTargetComponent() != null)) {
2445 packagesToIgnore.add(si.getTargetComponent().getPackageName());
2446 }
2447 } else if (info instanceof LauncherAppWidgetInfo) {
2448 LauncherAppWidgetInfo lawi = ((LauncherAppWidgetInfo) (info));
2449 if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2450 packagesToIgnore.add(lawi.providerName.getPackageName());
2451 }
2452 }
2453 }
2454 }
2455 mIconCache.updateDbIcons(packagesToIgnore);
2456 }
2457
2458 private void onlyBindAllApps() {
2459 final Callbacks oldCallbacks = mCallbacks.get();
2460 if (oldCallbacks == null) {
2461 // This launcher has exited and nobody bothered to tell us. Just bail.
2462 Log.w(TAG, "LoaderTask running with no launcher (onlyBindAllApps)");
2463 return;
2464 }
2465 // shallow copy
2466 @SuppressWarnings("unchecked")
2467 final ArrayList<AppInfo> list = ((ArrayList<AppInfo>) (mBgAllAppsList.data.clone()));
2468 final WidgetsModel widgetList = mBgWidgetsModel.clone();
2469 Runnable r = new Runnable() {
2470 public void run() {
2471 final long t = SystemClock.uptimeMillis();
2472 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2473 if (callbacks != null) {
2474 callbacks.bindAllApplications(list);
2475 callbacks.bindAllPackages(widgetList);
2476 }
2477 if (DEBUG_LOADERS) {
2478 Log.d(TAG, ((("bound all " + list.size()) + " apps from cache in ") + (SystemCloc🔵
2479 }
2480 }
2481 };
2482 boolean isRunningOnMainThread = !(sWorkerThread.getThreadId() == Process.myTid());
2483 if (isRunningOnMainThread) {
2484 r.run();
2485 } else {
2486 mHandler.post(r);
2487 }
2488 }
2489
2490 private void loadAllApps() {
2491 final long loadTime = (DEBUG_LOADERS) ? SystemClock.uptimeMillis() : 0;
2492 final Callbacks oldCallbacks = mCallbacks.get();
2493 if (oldCallbacks == null) {
2494 // This launcher has exited and nobody bothered to tell us. Just bail.
2495 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
2496 return;
2497 }
2498 final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();
2499 // Clear the list of apps
2500 mBgAllAppsList.clear();
2501 for (UserHandleCompat user : profiles) {
2502 // Query for the set of apps
2503 final long qiaTime = (DEBUG_LOADERS) ? SystemClock.uptimeMillis() : 0;
2504 final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
2505 if (DEBUG_LOADERS) {
2506 Log.d(TAG, (("getActivityList took " + (SystemClock.uptimeMillis() - qiaTime)) + "ms 🔵
2507 Log.d(TAG, (("getActivityList got " + apps.size()) + " apps for user ") + user);
2508 }
2509 // Fail if we don't have any apps
2510 // TODO: Fix this. Only fail for the current user.
2511 if ((apps == null) || apps.isEmpty()) {
2512 return;
2513 }
2514 // Create the ApplicationInfos
2515 for (int i = 0; i < apps.size(); i++) {
2516 LauncherActivityInfoCompat app = apps.get(i);
2517 // This builds the icon bitmaps.
2518 mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
2519 }
2520 final ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(mContext, user);
2521 if (heuristic != null) {
2522 runAfterBindCompletes(new Runnable() {
2523 @Override
2524 public void run() {
2525 heuristic.processUserApps(apps);
2526 }
2527 });
2528 }
2529 }
2530 // Huh? Shouldn't this be inside the Runnable below?
2531 final ArrayList<AppInfo> added = mBgAllAppsList.added;
2532 mBgAllAppsList.added = new ArrayList<AppInfo>();
2533 // Post callback on main thread
2534 mHandler.post(new Runnable() {
2535 public void run() {
2536 final long bindTime = SystemClock.uptimeMillis();
2537 final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
2538 if (callbacks != null) {
2539 callbacks.bindAllApplications(added);
2540 if (DEBUG_LOADERS) {
2541 Log.d(TAG, ((("bound " + added.size()) + " apps in ") + (SystemClock.uptimeMi🔵
2542 }
2543 } else {
2544 Log.i(TAG, "not binding apps: no Launcher activity");
2545 }
2546 }
2547 });
2548 // Cleanup any data stored for a deleted user.
2549 ManagedProfileHeuristic.processAllUsers(profiles, mContext);
2550 /* refresh */
2551 loadAndBindWidgetsAndShortcuts(mApp.getContext(), tryGetCallbacks(oldCallbacks), true);
2552 if (DEBUG_LOADERS) {
2553 Log.d(TAG, ("Icons processed in " + (SystemClock.uptimeMillis() - loadTime)) + "ms");
2554 }
2555 }
2556
2557 public void dumpState() {
2558 synchronized(sBgLock) {
2559 Log.d(TAG, "mLoaderTask.mContext=" + mContext);
2560 Log.d(TAG, "mLoaderTask.mStopped=" + mStopped);
2561 Log.d(TAG, "mLoaderTask.mLoadAndBindStepFinished=" + mLoadAndBindStepFinished);
2562 Log.d(TAG, "mItems size=" + sBgWorkspaceItems.size());
2563 }
2564 }
2565 }
2566
2567 /**
2568 * Called when the icons for packages have been updated in the icon cache.
2569 */
2570 public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) {
2571 final Callbacks callbacks = getCallback();
2572 final ArrayList<AppInfo> updatedApps = new ArrayList<>();
2573 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
2574 // If any package icon has changed (app was updated while launcher was dead),
2575 // update the corresponding shortcuts.
2576 synchronized(sBgLock) {
2577 for (ItemInfo info : sBgItemsIdMap) {
2578 if (((info instanceof ShortcutInfo) && user.equals(info.user)) && (info.itemType == Launc🔵
2579 ShortcutInfo si = ((ShortcutInfo) (info));
2580 ComponentName cn = si.getTargetComponent();
2581 if ((cn != null) && updatedPackages.contains(cn.getPackageName())) {
2582 si.updateIcon(mIconCache);
2583 updatedShortcuts.add(si);
2584 }
2585 }
2586 }
2587 mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps);
2588 }
2589 if (!updatedShortcuts.isEmpty()) {
2590 final UserHandleCompat userFinal = user;
2591 mHandler.post(new Runnable() {
2592 public void run() {
2593 Callbacks cb = getCallback();
2594 if ((cb != null) && (callbacks == cb)) {
2595 cb.bindShortcutsChanged(updatedShortcuts, new ArrayList<ShortcutInfo>(), userFina🔵
2596 }
2597 }
2598 });
2599 }
2600 if (!updatedApps.isEmpty()) {
2601 mHandler.post(new Runnable() {
2602 public void run() {
2603 Callbacks cb = getCallback();
2604 if ((cb != null) && (callbacks == cb)) {
2605 cb.bindAppsUpdated(updatedApps);
2606 }
2607 }
2608 });
2609 }
2610 // Reload widget list. No need to refresh, as we only want to update the icons and labels.
2611 loadAndBindWidgetsAndShortcuts(mApp.getContext(), callbacks, false);
2612 }
2613
2614 void enqueuePackageUpdated(PackageUpdatedTask task) {
2615 sWorker.post(task);
2616 }
2617
2618 @Thunk
2619 class AppsAvailabilityCheck extends BroadcastReceiver {
2620 @Override
2621 public void onReceive(Context context, Intent intent) {
2622 synchronized (sBgLock) {
2623 final LauncherAppsCompat launcherApps = LauncherAppsCompat
2624 .getInstance(mApp.getContext());
2625 final PackageManager manager = context.getPackageManager();
2626 final ArrayList<String> packagesRemoved = new ArrayList<String>();
2627 final ArrayList<String> packagesUnavailable = new ArrayList<String>();
2628 for (Entry<UserHandleCompat, HashSet<String>> entry : sPendingPackages.entrySet()) {
2629 UserHandleCompat user = entry.getKey();
2630 packagesRemoved.clear();
2631 packagesUnavailable.clear();
2632 for (String pkg : entry.getValue()) {
2633 if (!launcherApps.isPackageEnabledForProfile(pkg, user)) {
2634 boolean packageOnSdcard = launcherApps.isAppEnabled(
2635 manager, pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
2636 if (packageOnSdcard) {
2637 Launcher.addDumpLog(TAG, "Package found on sd-card: " + pkg, true);
2638 packagesUnavailable.add(pkg);
2639 } else {
2640 Launcher.addDumpLog(TAG, "Package not found: " + pkg, true);
2641 packagesRemoved.add(pkg);
2642 }
2643 }
2644 }
2645 if (!packagesRemoved.isEmpty()) {
2646 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_REMOVE,
2647 packagesRemoved.toArray(new String[packagesRemoved.size()]), user));
2648 }
2649 if (!packagesUnavailable.isEmpty()) {
2650 enqueuePackageUpdated(new PackageUpdatedTask(PackageUpdatedTask.OP_UNAVAILABLE,
2651 packagesUnavailable.toArray(new String[packagesUnavailable.size()]), user🔵
2652 }
2653 }
2654 sPendingPackages.clear();
2655 }
2656 }
2657 }
2658
2659 private class PackageUpdatedTask implements Runnable {
2660 int mOp;
2661
2662 String[] mPackages;
2663
2664 UserHandleCompat mUser;
2665
2666 public static final int OP_NONE = 0;
2667
2668 public static final int OP_ADD = 1;
2669
2670 public static final int OP_UPDATE = 2;
2671
2672 // uninstlled
2673 public static final int OP_REMOVE = 3; // uninstlled
2674
2675 // external media unmounted
2676 public static final int OP_UNAVAILABLE = 4; // external media unmounted
2677
2678 public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) {
2679 mOp = op;
2680 mPackages = packages;
2681 mUser = user;
2682 }
2683
2684 public void run() {
2685 if (!mHasLoaderCompletedOnce) {
2686 // Loader has not yet run.
2687 return;
2688 }
2689 final Context context = mApp.getContext();
2690 final String[] packages = mPackages;
2691 final int N = packages.length;
2692 switch (mOp) {
2693 case OP_ADD :
2694 {
2695 for (int i = 0; i < N; i++) {
2696 if (DEBUG_LOADERS) {
2697 Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
2698 }
2699 mIconCache.updateIconsForPkg(packages[i], mUser);
2700 mBgAllAppsList.addPackage(context, packages[i], mUser);
2701 }
2702 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
2703 if (heuristic != null) {
2704 heuristic.processPackageAdd(mPackages);
2705 }
2706 break;
2707 }
2708 case OP_UPDATE :
2709 for (int i = 0; i < N; i++) {
2710 if (DEBUG_LOADERS) {
2711 Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
2712 }
2713 mIconCache.updateIconsForPkg(packages[i], mUser);
2714 mBgAllAppsList.updatePackage(context, packages[i], mUser);
2715 mApp.getWidgetCache().removePackage(packages[i], mUser);
2716 }
2717 break;
2718 case OP_REMOVE :
2719 {
2720 ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
2721 if (heuristic != null) {
2722 heuristic.processPackageRemoved(mPackages);
2723 }
2724 for (int i = 0; i < N; i++) {
2725 if (DEBUG_LOADERS) {
2726 Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
2727 }
2728 mIconCache.removeIconsForPkg(packages[i], mUser);
2729 }
2730 // Fall through
2731 }
2732 case OP_UNAVAILABLE :
2733 for (int i = 0; i < N; i++) {
2734 if (DEBUG_LOADERS) {
2735 Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
2736 }
2737 mBgAllAppsList.removePackage(packages[i], mUser);
2738 mApp.getWidgetCache().removePackage(packages[i], mUser);
2739 }
2740 break;
2741 }
2742 ArrayList<AppInfo> added = null;
2743 ArrayList<AppInfo> modified = null;
2744 final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
2745 if (mBgAllAppsList.added.size() > 0) {
2746 added = new ArrayList<AppInfo>(mBgAllAppsList.added);
2747 mBgAllAppsList.added.clear();
2748 }
2749 if (mBgAllAppsList.modified.size() > 0) {
2750 modified = new ArrayList<AppInfo>(mBgAllAppsList.modified);
2751 mBgAllAppsList.modified.clear();
2752 }
2753 if (mBgAllAppsList.removed.size() > 0) {
2754 removedApps.addAll(mBgAllAppsList.removed);
2755 mBgAllAppsList.removed.clear();
2756 }
2757 final Callbacks callbacks = getCallback();
2758 if (callbacks == null) {
2759 Log.w(TAG, "Nobody to tell about the new app. Launcher is probably loading.");
2760 return;
2761 }
2762 final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<ComponentName, AppInfo🔵
2763 if (added != null) {
2764 addAppsToAllApps(context, added);
2765 for (AppInfo ai : added) {
2766 addedOrUpdatedApps.put(ai.componentName, ai);
2767 }
2768 }
2769 if (modified != null) {
2770 final ArrayList<AppInfo> modifiedFinal = modified;
2771 for (AppInfo ai : modified) {
2772 addedOrUpdatedApps.put(ai.componentName, ai);
2773 }
2774 mHandler.post(new Runnable() {
2775 public void run() {
2776 Callbacks cb = getCallback();
2777 if ((callbacks == cb) && (cb != null)) {
2778 callbacks.bindAppsUpdated(modifiedFinal);
2779 }
2780 }
2781 });
2782 }
2783 // Update shortcut infos
2784 if ((mOp == OP_ADD) || (mOp == OP_UPDATE)) {
2785 final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<ShortcutInfo>();
2786 final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<ShortcutInfo>();
2787 final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<LauncherAppWidgetInfo>();
2788 HashSet<String> packageSet = new HashSet<String>(Arrays.asList(packages));
2789 synchronized(sBgLock) {
2790 for (ItemInfo info : sBgItemsIdMap) {
2791 if ((info instanceof ShortcutInfo) && mUser.equals(info.user)) {
2792 ShortcutInfo si = ((ShortcutInfo) (info));
2793 boolean infoUpdated = false;
2794 boolean shortcutUpdated = false;
2795 // Update shortcuts which use iconResource.
2796 if ((si.iconResource != null) && packageSet.contains(si.iconResource.packageN🔵
2797 Bitmap icon = Utilities.createIconBitmap(si.iconResource.packageName, si.🔵
2798 if (icon != null) {
2799 si.setIcon(icon);
2800 si.usingFallbackIcon = false;
2801 infoUpdated = true;
2802 }
2803 }
2804 ComponentName cn = si.getTargetComponent();
2805 if ((cn != null) && packageSet.contains(cn.getPackageName())) {
2806 AppInfo appInfo = addedOrUpdatedApps.get(cn);
2807 if (si.isPromise()) {
2808 if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
2809 // Auto install icon
2810 PackageManager pm = context.getPackageManager();
2811 ResolveInfo matched = pm.resolveActivity(new Intent(Intent.ACTION🔵
2812 if (matched == null) {
2813 // Try to find the best match activity.
2814 Intent intent = pm.getLaunchIntentForPackage(cn.getPackageNam🔵
2815 if (intent != null) {
2816 cn = intent.getComponent();
2817 appInfo = addedOrUpdatedApps.get(cn);
2818 }
2819 if ((intent == null) || (appInfo == null)) {
2820 removedShortcuts.add(si);
2821 continue;
2822 }
2823 si.promisedIntent = intent;
2824 }
2825 }
2826 // Restore the shortcut.
2827 if (appInfo != null) {
2828 si.flags = appInfo.flags;
2829 }
2830 si.intent = si.promisedIntent;
2831 si.promisedIntent = null;
2832 si.status = ShortcutInfo.DEFAULT;
2833 infoUpdated = true;
2834 si.updateIcon(mIconCache);
2835 }
2836 if (((appInfo != null) && Intent.ACTION_MAIN.equals(si.intent.getAction()🔵
2837 si.updateIcon(mIconCache);
2838 si.title = Utilities.trim(appInfo.title);
2839 si.contentDescription = appInfo.contentDescription;
2840 infoUpdated = true;
2841 }
2842 if ((si.isDisabled & ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE) != 0) {
2843 // Since package was just updated, the target must be available now.
2844 si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
2845 shortcutUpdated = true;
2846 }
2847 }
2848 if (infoUpdated || shortcutUpdated) {
2849 updatedShortcuts.add(si);
2850 }
2851 if (infoUpdated) {
2852 updateItemInDatabase(context, si);
2853 }
2854 } else if (info instanceof LauncherAppWidgetInfo) {
2855 LauncherAppWidgetInfo widgetInfo = ((LauncherAppWidgetInfo) (info));
2856 if ((mUser.equals(widgetInfo.user) && widgetInfo.hasRestoreFlag(LauncherAppWi🔵
2857 widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READ🔵
2858 widgets.add(widgetInfo);
2859 updateItemInDatabase(context, widgetInfo);
2860 }
2861 }
2862 }
2863 }
2864 if ((!updatedShortcuts.isEmpty()) || (!removedShortcuts.isEmpty())) {
2865 mHandler.post(new Runnable() {
2866 public void run() {
2867 Callbacks cb = getCallback();
2868 if ((callbacks == cb) && (cb != null)) {
2869 callbacks.bindShortcutsChanged(updatedShortcuts, removedShortcuts, mUser)🔵
2870 }
2871 }
2872 });
2873 if (!removedShortcuts.isEmpty()) {
2874 deleteItemsFromDatabase(context, removedShortcuts);
2875 }
2876 }
2877 if (!widgets.isEmpty()) {
2878 mHandler.post(new Runnable() {
2879 public void run() {
2880 Callbacks cb = getCallback();
2881 if ((callbacks == cb) && (cb != null)) {
2882 callbacks.bindWidgetsRestored(widgets);
2883 }
2884 }
2885 });
2886 }
2887 }
2888 final ArrayList<String> removedPackageNames = new ArrayList<String>();
2889 if ((mOp == OP_REMOVE) || (mOp == OP_UNAVAILABLE)) {
2890 // Mark all packages in the broadcast to be removed
2891 removedPackageNames.addAll(Arrays.asList(packages));
2892 } else if (mOp == OP_UPDATE) {
2893 // Mark disabled packages in the broadcast to be removed
2894 for (int i = 0; i < N; i++) {
2895 if (isPackageDisabled(context, packages[i], mUser)) {
2896 removedPackageNames.add(packages[i]);
2897 }
2898 }
2899 }
2900 if ((!removedPackageNames.isEmpty()) || (!removedApps.isEmpty())) {
2901 final int removeReason;
2902 if (mOp == OP_UNAVAILABLE) {
2903 removeReason = ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE;
2904 } else {
2905 // Remove all the components associated with this package
2906 for (String pn : removedPackageNames) {
2907 deletePackageFromDatabase(context, pn, mUser);
2908 }
2909 // Remove all the specific components
2910 for (AppInfo a : removedApps) {
2911 ArrayList<ItemInfo> infos = getItemInfoForComponentName(a.componentName, mUser);
2912 deleteItemsFromDatabase(context, infos);
2913 }
2914 removeReason = 0;
2915 }
2916 // Remove any queued items from the install queue
2917 InstallShortcutReceiver.removeFromInstallQueue(context, removedPackageNames, mUser);
2918 // Call the components-removed callback
2919 mHandler.post(new Runnable() {
2920 public void run() {
2921 Callbacks cb = getCallback();
2922 if ((callbacks == cb) && (cb != null)) {
2923 callbacks.bindComponentsRemoved(removedPackageNames, removedApps, mUser, remo🔵
2924 }
2925 }
2926 });
2927 }
2928 // onProvidersChanged method (API >= 17) already refreshed the widget list
2929 loadAndBindWidgetsAndShortcuts(context, callbacks, Build.VERSION.SDK_INT < 17);
2930 // Write all the logs to disk
2931 mHandler.post(new Runnable() {
2932 public void run() {
2933 Callbacks cb = getCallback();
2934 if ((callbacks == cb) && (cb != null)) {
2935 callbacks.dumpLogsToLocalData();
2936 }
2937 }
2938 });
2939 }
2940 }
2941
2942 public static List<LauncherAppWidgetProviderInfo> getWidgetProviders(Context context,
2943 boolean refresh) {
2944 ArrayList<LauncherAppWidgetProviderInfo> results =
2945 new ArrayList<LauncherAppWidgetProviderInfo>();
2946 try {
2947 synchronized (sBgLock) {
2948 if (sBgWidgetProviders == null || refresh) {
2949 HashMap<ComponentKey, LauncherAppWidgetProviderInfo> tmpWidgetProviders
2950 = new HashMap<>();
2951 AppWidgetManagerCompat wm = AppWidgetManagerCompat.getInstance(context);
2952 LauncherAppWidgetProviderInfo info;
2953
2954 List<AppWidgetProviderInfo> widgets = wm.getAllProviders();
2955 for (AppWidgetProviderInfo pInfo : widgets) {
2956 info = LauncherAppWidgetProviderInfo.fromProviderInfo(context, pInfo);
2957 UserHandleCompat user = wm.getUser(info);
2958 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
2959 }
2960
2961 Collection<CustomAppWidget> customWidgets = Launcher.getCustomAppWidgets().values();
2962 for (CustomAppWidget widget : customWidgets) {
2963 info = new LauncherAppWidgetProviderInfo(context, widget);
2964 UserHandleCompat user = wm.getUser(info);
2965 tmpWidgetProviders.put(new ComponentKey(info.provider, user), info);
2966 }
2967 // Replace the global list at the very end, so that if there is an exception,
2968 // previously loaded provider list is used.
2969 sBgWidgetProviders = tmpWidgetProviders;
2970 }
2971 results.addAll(sBgWidgetProviders.values());
2972 return results;
2973 }
2974 } catch (Exception e) {
2975 if (e.getCause() instanceof TransactionTooLargeException) {
2976 // the returned value may be incomplete and will not be refreshed until the next
2977 // time Launcher starts.
2978 // TODO: after figuring out a repro step, introduce a dirty bit to check when
2979 // onResume is called to refresh the widget provider list.
2980 synchronized (sBgLock) {
2981 if (sBgWidgetProviders != null) {
2982 results.addAll(sBgWidgetProviders.values());
2983 }
2984 return results;
2985 }
2986 } else {
2987 throw e;
2988 }
2989 }
2990 }
2991
2992 public static LauncherAppWidgetProviderInfo getProviderInfo(Context ctx, ComponentName name,
2993 UserHandleCompat user) {
2994 synchronized (sBgLock) {
2995 if (sBgWidgetProviders == null) {
2996 getWidgetProviders(ctx, false /* refresh */);
2997 }
2998 return sBgWidgetProviders.get(new ComponentKey(name, user));
2999 }
3000 }
3001
3002 public void loadAndBindWidgetsAndShortcuts(final Context context, final Callbacks callbacks, final bo🔵
3003 runOnWorkerThread(new Runnable() {
3004 @Override
3005 public void run() {
3006 updateWidgetsModel(context, refresh);
3007 final WidgetsModel model = mBgWidgetsModel.clone();
3008 mHandler.post(new Runnable() {
3009 @Override
3010 public void run() {
3011 Callbacks cb = getCallback();
3012 if ((callbacks == cb) && (cb != null)) {
3013 callbacks.bindAllPackages(model);
3014 }
3015 }
3016 });
3017 // update the Widget entries inside DB on the worker thread.
3018 LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(model.getRawList()🔵
3019 }
3020 });
3021 }
3022
3023 /**
3024 * Returns a list of ResolveInfos/AppWidgetInfos.
3025 *
3026 * @see #loadAndBindWidgetsAndShortcuts
3027 */
3028 @Thunk
3029 void updateWidgetsModel(Context context, boolean refresh) {
3030 PackageManager packageManager = context.getPackageManager();
3031 final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
3032 widgetsAndShortcuts.addAll(getWidgetProviders(context, refresh));
3033 Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
3034 widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
3035 mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
3036 }
3037
3038 @Thunk
3039 static boolean isPackageDisabled(Context context, String packageName, UserHandleCompat user) {
3040 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3041 return !launcherApps.isPackageEnabledForProfile(packageName, user);
3042 }
3043
3044 public static boolean isValidPackageActivity(Context context, ComponentName cn,
3045 UserHandleCompat user) {
3046 if (cn == null) {
3047 return false;
3048 }
3049 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3050 if (!launcherApps.isPackageEnabledForProfile(cn.getPackageName(), user)) {
3051 return false;
3052 }
3053 return launcherApps.isActivityEnabledForProfile(cn, user);
3054 }
3055
3056 public static boolean isValidPackage(Context context, String packageName,
3057 UserHandleCompat user) {
3058 if (packageName == null) {
3059 return false;
3060 }
3061 final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
3062 return launcherApps.isPackageEnabledForProfile(packageName, user);
3063 }
3064
3065 /**
3066 * Make an ShortcutInfo object for a restored application or shortcut item that points
3067 * to a package that is not yet installed on the system.
3068 */
3069 public ShortcutInfo getRestoredItemInfo(Cursor c, int titleIndex, Intent intent, int promiseType, int🔵
3070 final ShortcutInfo info = new ShortcutInfo();
3071 info.user = UserHandleCompat.myUserHandle();
3072 Bitmap icon = iconInfo.loadIcon(c, info, context);
3073 // the fallback icon
3074 if (icon == null) {
3075 /* useLowResIcon */
3076 mIconCache.getTitleAndIcon(info, intent, info.user, false);
3077 } else {
3078 info.setIcon(icon);
3079 }
3080 if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
3081 String title = (c != null) ? c.getString(titleIndex) : null;
3082 if (!TextUtils.isEmpty(title)) {
3083 info.title = Utilities.trim(title);
3084 }
3085 } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
3086 if (TextUtils.isEmpty(info.title)) {
3087 info.title = (c != null) ? Utilities.trim(c.getString(titleIndex)) : "";
3088 }
3089 } else {
3090 throw new InvalidParameterException("Invalid restoreType " + promiseType);
3091 }
3092 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3093 info.itemType = itemType;
3094 info.promisedIntent = intent;
3095 info.status = promiseType;
3096 return info;
3097 }
3098
3099 /**
3100 * Make an Intent object for a restored application or shortcut item that points
3101 * to the market page for the item.
3102 */
3103 @Thunk
3104 Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) {
3105 ComponentName componentName = intent.getComponent();
3106 return getMarketIntent(componentName.getPackageName());
3107 }
3108
3109 static Intent getMarketIntent(String packageName) {
3110 return new Intent(Intent.ACTION_VIEW)
3111 .setData(new Uri.Builder()
3112 .scheme("market")
3113 .authority("details")
3114 .appendQueryParameter("id", packageName)
3115 .build());
3116 }
3117
3118 /**
3119 * Make an ShortcutInfo object for a shortcut that is an application.
3120 *
3121 * If c is not null, then it will be used to fill in missing data like the title and icon.
3122 */
3123 public ShortcutInfo getAppShortcutInfo(PackageManager manager, Intent intent, UserHandleCompat user, 🔵
3124 if (user == null) {
3125 Log.d(TAG, "Null user found in getShortcutInfo");
3126 return null;
3127 }
3128 ComponentName componentName = intent.getComponent();
3129 if (componentName == null) {
3130 Log.d(TAG, "Missing component found in getShortcutInfo: " + componentName);
3131 return null;
3132 }
3133 Intent newIntent = new Intent(intent.getAction(), null);
3134 newIntent.addCategory(Intent.CATEGORY_LAUNCHER);
3135 newIntent.setComponent(componentName);
3136 LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user);
3137 if ((lai == null) && (!allowMissingTarget)) {
3138 Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName);
3139 return null;
3140 }
3141 final ShortcutInfo info = new ShortcutInfo();
3142 mIconCache.getTitleAndIcon(info, componentName, lai, user, false, useLowResIcon);
3143 if (mIconCache.isDefaultIcon(info.getIcon(mIconCache), user) && (c != null)) {
3144 Bitmap icon = Utilities.createIconBitmap(c, iconIndex, context);
3145 info.setIcon(icon == null ? mIconCache.getDefaultIcon(user) : icon);
3146 }
3147 // from the db
3148 if (TextUtils.isEmpty(info.title) && (c != null)) {
3149 info.title = Utilities.trim(c.getString(titleIndex));
3150 }
3151 // fall back to the class name of the activity
3152 if (info.title == null) {
3153 info.title = componentName.getClassName();
3154 }
3155 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
3156 info.user = user;
3157 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3158 if (lai != null) {
3159 info.flags = AppInfo.initFlags(lai);
3160 }
3161 return info;
3162 }
3163
3164 static ArrayList<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos, ItemInfoFilter f) {
3165 HashSet<ItemInfo> filtered = new HashSet<ItemInfo>();
3166 for (ItemInfo i : infos) {
3167 if (i instanceof ShortcutInfo) {
3168 ShortcutInfo info = ((ShortcutInfo) (i));
3169 ComponentName cn = info.getTargetComponent();
3170 if ((cn != null) && f.filterItem(null, info, cn)) {
3171 filtered.add(info);
3172 }
3173 } else if (i instanceof FolderInfo) {
3174 FolderInfo info = ((FolderInfo) (i));
3175 for (ShortcutInfo s : info.contents) {
3176 ComponentName cn = s.getTargetComponent();
3177 if ((cn != null) && f.filterItem(info, s, cn)) {
3178 filtered.add(s);
3179 }
3180 }
3181 } else if (i instanceof LauncherAppWidgetInfo) {
3182 LauncherAppWidgetInfo info = ((LauncherAppWidgetInfo) (i));
3183 ComponentName cn = info.providerName;
3184 if ((cn != null) && f.filterItem(null, info, cn)) {
3185 filtered.add(info);
3186 }
3187 }
3188 }
3189 return new ArrayList<ItemInfo>(filtered);
3190 }
3191
3192 @Thunk
3193 ArrayList<ItemInfo> getItemInfoForComponentName(final ComponentName cname, final UserHandleCompat use🔵
3194 ItemInfoFilter filter = new ItemInfoFilter() {
3195 @Override
3196 public boolean filterItem(ItemInfo parent, ItemInfo info, ComponentName cn) {
3197 if (info.user == null) {
3198 return cn.equals(cname);
3199 } else {
3200 return cn.equals(cname) && info.user.equals(user);
3201 }
3202 }
3203 };
3204 return filterItemInfos(sBgItemsIdMap, filter);
3205 }
3206
3207 /**
3208 * Make an ShortcutInfo object for a shortcut that isn't an application.
3209 */
3210 @Thunk
3211 ShortcutInfo getShortcutInfo(Cursor c, Context context, int titleIndex, CursorIconInfo iconInfo) {
3212 final ShortcutInfo info = new ShortcutInfo();
3213 // Non-app shortcuts are only supported for current user.
3214 info.user = UserHandleCompat.myUserHandle();
3215 info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
3216 // TODO: If there's an explicit component and we can't install that, delete it.
3217 info.title = Utilities.trim(c.getString(titleIndex));
3218 Bitmap icon = iconInfo.loadIcon(c, info, context);
3219 // the fallback icon
3220 if (icon == null) {
3221 icon = mIconCache.getDefaultIcon(info.user);
3222 info.usingFallbackIcon = true;
3223 }
3224 info.setIcon(icon);
3225 return info;
3226 }
3227
3228 ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
3229 Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
3230 String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
3231 Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
3232 if (intent == null) {
3233 // If the intent is null, we can't construct a valid ShortcutInfo, so we return null
3234 Log.e(TAG, "Can't construct ShorcutInfo with null intent");
3235 return null;
3236 }
3237 Bitmap icon = null;
3238 boolean customIcon = false;
3239 ShortcutIconResource iconResource = null;
3240 if (bitmap instanceof Bitmap) {
3241 icon = Utilities.createIconBitmap(((Bitmap) (bitmap)), context);
3242 customIcon = true;
3243 } else {
3244 Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
3245 if (extra instanceof ShortcutIconResource) {
3246 iconResource = ((ShortcutIconResource) (extra));
3247 icon = Utilities.createIconBitmap(iconResource.packageName, iconResource.resourceName, co🔵
3248 }
3249 }
3250 final ShortcutInfo info = new ShortcutInfo();
3251 // Only support intents for current user for now. Intents sent from other
3252 // users wouldn't get here without intent forwarding anyway.
3253 info.user = UserHandleCompat.myUserHandle();
3254 if (icon == null) {
3255 icon = mIconCache.getDefaultIcon(info.user);
3256 info.usingFallbackIcon = true;
3257 }
3258 info.setIcon(icon);
3259 info.title = Utilities.trim(name);
3260 info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
3261 info.intent = intent;
3262 info.customIcon = customIcon;
3263 info.iconResource = iconResource;
3264 return info;
3265 }
3266
3267 /**
3268 * Return an existing FolderInfo object if we have encountered this ID previously,
3269 * or make a new one.
3270 */
3271 @Thunk
3272 static FolderInfo findOrMakeFolder(LongArrayMap<FolderInfo> folders, long id) {
3273 // See if a placeholder was created for us already
3274 FolderInfo folderInfo = folders.get(id);
3275 if (folderInfo == null) {
3276 // No placeholder -- create a new instance
3277 folderInfo = new FolderInfo();
3278 folders.put(id, folderInfo);
3279 }
3280 return folderInfo;
3281 }
3282
3283 static boolean isValidProvider(AppWidgetProviderInfo provider) {
3284 return (provider != null) && (provider.provider != null)
3285 && (provider.provider.getPackageName() != null);
3286 }
3287
3288 public void dumpState() {
3289 Log.d(TAG, "mCallbacks=" + mCallbacks);
3290 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data);
3291 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.added", mBgAllAppsList.added);
3292 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.removed", mBgAllAppsList.removed);
3293 AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.modified", mBgAllAppsList.modified);
3294 if (mLoaderTask != null) {
3295 mLoaderTask.dumpState();
3296 } else {
3297 Log.d(TAG, "mLoaderTask=null");
3298 }
3299 }
3300
3301 public Callbacks getCallback() {
3302 return mCallbacks != null ? mCallbacks.get() : null;
3303 }
3304
3305 /**
3306 * @return {@link FolderInfo} if its already loaded.
3307 */
3308 public FolderInfo findFolderById(Long folderId) {
3309 synchronized(sBgLock) {
3310 return sBgFolders.get(folderId);
3311 }
3312 }
3313
3314 /**
3315 * @return the looper for the worker thread which can be used to start background tasks.
3316 */
3317 public static Looper getWorkerLooper() {
3318 return sWorkerThread.getLooper();
3319 }
3320 }
|